fix(agent-backend): keep a bounded stderr tail on failed Agent Turns - #1069
Merged
berenddeboer merged 1 commit intoAug 14, 2026
Conversation
When an Agent Backend CLI died with its only explanation on stderr, the operator never saw it. The originating case was an expired AWS SSO token: Claude Code exited non-zero in about two seconds, wrote the credential error only to stderr, emitted no JSONL `result`, and never tripped the 60s startup watchdog. `runCliTurn` had `stderr: "ignore"`, so the text was discarded and the Cause chain stopped at a bare `AgentBackendExitError`. #1056 already surfaces adapter-parsed reasons. This change is the missing capture path for text that exists only on stderr (#1057). **What changed** - Agent Turns pipe stderr and drain it inside the same raced `Effect.all` as stdout and `exitCode`, so a piped stream is never left undrained. - The fold keeps the last 4000 characters (same bound as Install Dependencies). - On non-zero exit, if the adapter supplied no reason, the tail is sanitized (ANSI stripped, token-shaped secrets redacted) and then truncated to the last 500 characters as `AgentBackendExitError.message`. An adapter-supplied reason still wins. - A CLI that writes only to stderr still hits the startup watchdog. Finalize still tree-kills immediately; the fold cannot block that kill. **Verification** - `runCliTurn`: stderr-only exit message, adapter precedence, flood plus 4000-character fold bound, sanitization and a token that straddles the 500-character cut, finalize timing, stderr-only startup watchdog. - Implement: a fake CLI that writes only to stderr shows `AgentBackendExitError 1 — …` in `extractCauseChain`. - `agent-backend` tests and the Claude, Codex, Grok, and OpenCode adapter suites stayed green. **Limitations** - Silent non-zero exits with empty stderr still have no message (staged for #1066). - Fold-fiber leak is prevented by putting the drain in the race, not by an Effect child-fiber assertion (not observable after tree-kill). Closes #1057
berenddeboer
marked this pull request as ready for review
August 14, 2026 06:27
berenddeboer
deleted the
rfa/berenddeboer-ready-for-agent/1057/wi-01KZZDDXAF9M8P6DV8BT9WMBSX
branch
August 14, 2026 06:30
This was referenced Aug 14, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
When an Agent Backend CLI died with its only explanation on stderr, the
operator never saw it. The originating case was an expired AWS SSO token:
Claude Code exited non-zero in about two seconds, wrote the credential
error only to stderr, emitted no JSONL
result, and never tripped the 60sstartup watchdog.
runCliTurnhadstderr: "ignore", so the text wasdiscarded and the Cause chain stopped at a bare
AgentBackendExitError.#1056 already surfaces adapter-parsed reasons. This change is the missing
capture path for text that exists only on stderr (#1057).
What changed
Effect.allas stdout and
exitCode, so a piped stream is never left undrained.Dependencies).
sanitized (ANSI stripped, token-shaped secrets redacted) and then
truncated to the last 500 characters as
AgentBackendExitError.message.An adapter-supplied reason still wins.
Finalize still tree-kills immediately; the fold cannot block that kill.
Verification
runCliTurn: stderr-only exit message, adapter precedence, flood plus4000-character fold bound, sanitization and a token that straddles the
500-character cut, finalize timing, stderr-only startup watchdog.
AgentBackendExitError 1 — …inextractCauseChain.agent-backendtests and the Claude, Codex, Grok, and OpenCode adaptersuites stayed green.
Limitations
for Make AgentBackendExitError.message required via expand–contract #1066).
an Effect child-fiber assertion (not observable after tree-kill).
Closes #1057