Skip to content

fix(selfhost): surface the CLI's structured stdout error before the exit code (#1612)#1613

Merged
JSONbored merged 1 commit into
mainfrom
fix/selfhost-surface-cli-stdout-error
Jun 27, 2026
Merged

fix(selfhost): surface the CLI's structured stdout error before the exit code (#1612)#1613
JSONbored merged 1 commit into
mainfrom
fix/selfhost-surface-cli-stdout-error

Conversation

@JSONbored

Copy link
Copy Markdown
Owner

Summary

claude --output-format json reports API/auth/model errors in its stdout JSON envelope ({is_error:true, api_error_status:404, result:"…"}) — including on a non-zero exit, with empty stderr. createClaudeCodeAi.run threw on the exit code before checking that envelope, so such failures surfaced as an opaque claude_code_exit_1: (blank — stderr is empty), discarding the precise status.

This is exactly why #1610 (a claude --model claude-code 404 from the single-provider routing bug) was undiagnosable from logs/Sentry and needed in-container reproduction to root-cause. #1605 added stderr capture, but stderr is empty for this whole class of failure — the signal is in stdout.

Fix: check claudeErrorStatus(stdout) before the exit-code throw, so the structured error surfaces as claude_code_error_404 (precise, bounded, secret-free) and reaches the central Sentry forwarder. A genuine crash (non-zero exit with no structured stdout envelope) still falls through to the existing exit-code + redacted-stderr path. Two moved lines + a regression test; no public behavior, schema, binding, or migration change.

Closes #1612.

Scope

  • The PR title follows type(scope): short summary Conventional Commit format.
  • This PR is focused and does not mix unrelated backend, UI, MCP, docs, dependency, and deploy changes.
  • This follows CONTRIBUTING.md and does not reintroduce GitHub Pages, VitePress, site/, or CNAME.
  • I linked an issue, or this is small enough that the summary explains why an issue is not needed.

Validation

  • git diff --check
  • npm run actionlint
  • npm run typecheck
  • npm run test:coverage locally; codecov/patch ≥97% of changed lines AND branches — both branches of the moved claudeErrorStatus/exit-code checks are covered (exit-0 envelope, exit-1 envelope, exit-1 no-envelope, success).
  • npm run test:workers
  • npm run build:mcp
  • npm run test:mcp-pack
  • npm run ui:openapi:check
  • npm run ui:lint
  • npm run ui:typecheck
  • npm run ui:build
  • npm audit --audit-level=moderate
  • New or changed behavior has unit/integration tests for new branches, fallback paths, and sanitizer boundaries

If any required check was skipped, explain why:

  • No artifact regeneration needed: backend-only, no API/OpenAPI/schema change, no wrangler.jsonc change, no DB change.

Safety

  • No secrets, wallet details, hotkeys, coldkeys, user PATs, private keys, raw trust scores, private rankings, or private maintainer evidence are exposed. — the surfaced status is the bounded api_error_status (e.g. 404); the stderr fallback keeps the existing redactSecrets.
  • Public GitHub text stays sanitized, low-noise, and does not imply compensation guarantees or optimization tactics.
  • Auth, cookie, CORS, GitHub App, Cloudflare, or session changes include negative-path tests. — N/A: AI-provider error handling.
  • API/OpenAPI/MCP behavior is updated and tested where needed. — N/A.
  • UI changes use live API data or real empty/error/loading states. — N/A: no UI change.
  • Visible UI changes include a UI Evidence section. — N/A: no visible UI change.
  • Public docs/changelogs are updated where needed. — N/A.

Notes

…xit code (#1612)

claude --output-format json reports API/auth/model errors in its stdout
envelope ({is_error,api_error_status}) even on a non-zero exit, with empty
stderr. createClaudeCodeAi threw on the exit code BEFORE checking that
envelope, so the failure surfaced as an opaque "claude_code_exit_1: " (blank)
and the precise status was discarded — exactly why #1610's 404 was
undiagnosable from logs/Sentry.

Check claudeErrorStatus(stdout) before the exit-code throw so a structured
error surfaces as claude_code_error_<status> (e.g. _404). A genuine crash with
no structured stdout envelope still falls through to the exit code + redacted
stderr. Regression test pins the non-zero-exit + stdout-envelope case.
@dosubot dosubot Bot added the size:XS label Jun 27, 2026
@superagent-security

Copy link
Copy Markdown
Contributor

Superagent didn't find any vulnerabilities or security issues in this PR.

@codecov

codecov Bot commented Jun 27, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 95.51%. Comparing base (2ba452e) to head (00afd68).
✅ All tests successful. No failed tests found.

Additional details and impacted files
@@           Coverage Diff           @@
##             main    #1613   +/-   ##
=======================================
  Coverage   95.51%   95.51%           
=======================================
  Files         204      204           
  Lines       22041    22041           
  Branches     7963     7963           
=======================================
  Hits        21052    21052           
  Misses        413      413           
  Partials      576      576           
Files with missing lines Coverage Δ
src/selfhost/ai.ts 99.44% <100.00%> (ø)
🚀 New features to boost your workflow:
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

@JSONbored
JSONbored merged commit 050e751 into main Jun 27, 2026
28 of 30 checks passed
@JSONbored
JSONbored deleted the fix/selfhost-surface-cli-stdout-error branch June 27, 2026 18:35
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Self-host CLI reviews report opaque claude_code_exit_1 when the real error is in stdout

1 participant