Repository navigation
fix(server): Claude turns say Claude Code exited when it dies at startup - #26242
juliusmarminge wants to merge 3 commits into
Conversation
…xit and stderr A Claude CLI that writes to stderr and exits 1 before its first message fails the turn with only "Provider turn failed.", and the stream failure cause carries no trace of the CLI's stderr for the server log. These tests reproduce #16297 and fail on main. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A Claude CLI that exited before its first message (for example macOS
denying it the project folder with EPERM) failed the turn with only
"Provider turn failed.", and its stderr was discarded, so the server log
had no reason either.
The process-group spawn now pipes and always drains stderr, keeping a
2048-char tail until the first message arrives. A query that fails before
answering, after a nonzero exit or a signal, and is not an SDK abort,
becomes a curated ClaudeCodeStartupExitError ("Claude Code exited with
code 1 before it responded."). The stderr tail rides only in its cause
chain, so Cause.pretty in the stream-failure warning shows it while the
client message never does. Windows' own spawn feeds the same tail.
Fixes #16297
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
# Conflicts: # packages/provider-core/src/server/failure.ts
ApprovabilityVerdict: Approved at Macroscope's review found this PR approvable — Claude Code startup failures now show an exit code or signal, with a bounded stderr tail available through server logs rather than the user-facing message. The change is limited to failure diagnostics and preserves normal-response and explicit-stop handling. You can add or adjust custom eligibility rules. Learn more. |
Thread transfer impact✅ Thread transfer remains within every enforced ceiling.
Baseline: Scenario and decoded snapshot size10 historical turns, 5 command tools per turn, 878.9 KiB retained MCP result per historical turn, and a 1.05 MiB retained result in the measured turn.
Updated in place by a trusted workflow. PR artifacts are strictly validated and never executed. |
|
No actionable comments were generated in the recent review.
|
| Layer / File(s) | Summary |
|---|---|
Capture CLI stderr and exit status apps/server/src/orchestration-v2/Adapters/ClaudeAdapterV2.ts |
The process-group spawn pipes stderr to an optional callback. The runner retains up to 2,048 characters of stderr until the first response and records the process exit code and signal. |
Convert early exits to provider errors apps/server/src/orchestration-v2/Adapters/ClaudeAdapterV2.ts, packages/provider-core/src/server/failure.ts, apps/server/src/orchestration-v2/Adapters/ClaudeAgentSdkQueryRunner.test.ts |
For qualifying stream failures, the runner creates a startup error and attaches captured stderr to its cause. The provider failure mapper recognizes the error message. Tests check the reported exit code and retained stderr. |
Priority:
Estimated code review effort: 3 (Moderate) | ~20 minutes
Change: Bug fix
Sequence Diagram(s)
sequenceDiagram
participant ClaudeCLI
participant ClaudeAgentSdkQueryRunner
participant curatedCauseMessages
ClaudeCLI->>ClaudeAgentSdkQueryRunner: stderr and exit status before first response
ClaudeAgentSdkQueryRunner->>curatedCauseMessages: ClaudeCodeStartupExitError
curatedCauseMessages-->>ClaudeAgentSdkQueryRunner: known failure message
Possibly related issues
pingdotgg/t3code#26082— Both changes concern early Claude process exits that otherwise produce a generic client error; this change reports exit details and retains stderr.
Suggested reviewers: t3dotgg
Merge Risk: 
Low · up to 40f32
-
Early exits now report an exit code, but server logs may occasionally omit part of the startup diagnostic. This is a bounded diagnostic risk.
Pre-merge checks: 
3 · 
1
-
Failed checks: 1 warning Passed checks: 3 passed-
Full details: Out of Scope Changes check
-
Explanation
The changes stay within issue
#16297. Stderr capture, bounded retention, startup-exit classification, server-log cause details, failure-message curation, and regression tests all address diagnosis or handling of the reported early Claude turn failure. No unrelated product area or behavior is changed.
-
Full details: Linked Issues check
-
Explanation
Direct issue
#16297requires a Claude turn launched from T3 to behave like the same authenticated CLI and returnOK. The PR changes only failure reporting.ClaudeCodeStartupExitErrorstill fails a nonzero exit before the first message, and the added tests assert that failure. The PR description states that an actual macOS access-denial run was not verified and does not show that this condition now returnsOK.Resolution
Fix the startup condition that causes the macOS T3 launch to exit before the first message, so the affected authenticated CLI completes the turn and returns
OK. Add automated coverage for the relevant startup path and successful first response.
-
Finishing Touches
-
📝 Generate docstrings
-
- Commit to this branch
- Create a new PR
🧪 Generate unit tests (beta)
-
- Commit to this branch
- Create a new PR
-
- Autofix · Keep fixing CodeRabbit findings and required CI, and resolving merge conflicts
Comment @coderabbitai help to get the list of available commands.
…tup (upstream pingdotgg#26242) Cherry-picked from pingdotgg#26242 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Problem
When the Claude Code CLI exits before it sends its first message, the turn fails about 10 ms after
prompt.offerwith onlyProvider turn failed.(transport_error, no code). The cause is not recorded anywhere. One example is macOS denying the CLI access to the project folder, where it printserror: An internal error occurred (EPERM)and exits 1. T3 starts the CLI with stderr set toignore, so the CLI's own explanation is thrown away. The server log then shows only "Claude Code process exited with code 1".To reproduce on Linux, point a Claude instance at a CLI that writes that line to stderr and exits 1, then send any prompt. The turn fails with
Provider turn failed.and the EPERM line appears nowhere.Change
spawnClaudeProcessGroupnow pipes the CLI's stderr and always drains it. The query runner keeps up to 2,048 characters of it until the CLI's first message, the same limit the SDK uses when it starts the CLI itself.ClaudeCodeStartupExitError.failure.tslists it as a curated error, so the turn reads "Claude Code exited with code 1 before it responded. Check the server log for details."orchestration-v2.claude-query-stream-failedserver warning prints it. It never reaches the failure message clients see, because stderr can carry paths or tokens.AbortError), exit code 0, a spawn that fails before any exit, and a CLI that dies after it has answered all keep today's error. The Windows spawn path feeds the same tail.Scope and approval
Fixes #16297
Triaged via the automated issue flow; GitCafe issue #16297.
Verification
Tests
ClaudeAgentSdkQueryRunner.test.tsgains two tests that use the real query runner and a fake CLI. That CLI writes a private-looking EPERM line to stderr and exits 1.names Claude Code's exit code when it exits before answering: the failure message includes "exited" and "code 1" and not the stderr text.keeps the CLI's startup stderr for the server log:Cause.prettyincludes the stderr line.expected 'Provider turn failed.' to include 'exited'; stderr missing from the cause) and pass with the fix.failure.test.tssuites pass: 22 tests.vp run check:changedpassed at the merged head: format, lint, knip, typecheck of 23 packages, and 266 tests in 3 files.Runtime
This is a server-only fix with no render path, so there are no captures. I ran a real dev server on a fresh database,
t3 thread start --waitwith the reporter's prompt, and a stub CLI:36d4d711b7)0004ae9b87)Provider turn failed.Claude Code exited with code 1 before it responded. Check the server log for details.claude-query-stream-failedcauseClaude Code process exited with code 1ClaudeCodeStartupExitError→stderr: error: An internal error occurred (EPERM)→ SDK exit errorOKOKRuntime transcript · stub CLI
Not checked
cannot-verify macos: I did not run an actual macOS folder-access denial. The stub reproduces its observable shape (stderr line, exit 1, no SDK frames).Models: Claude Opus 5.5 and GPT-6.1-Sol; harnesses: Claude Agent SDK and Codex through T3 Code.
🤖 Generated with Claude Code