strip ANSI from headless agent output so a slow run stops looking wedged - #6118
Merged
Conversation
A public-review Stage 3 run reported as stuck. It was not: the OpenCode child was nine minutes into prefilling a ~100K-token prompt on a local 27B model. The card gave no way to tell, for two reasons, both fixed here. `opencode run` wraps its status line in bare SGR resets. Those bytes reached output.txt and the live tail verbatim, and the browser drops only the ESC — so the only thing on the card was `[stderr] [0m`. Both headless spawners now decolor per stream through the shared `createStreamingAnsiStripper`, before the fallback-signal detector and the codex stderr formatter (each matches provider prose that an SGR pair would otherwise split). The runner in `cos-runner` is a twin of `spawnDirectly`'s handlers and had the identical bug, so it gets the same treatment; a stream-json stdout is NDJSON with no raw ESC and skips the scan. A chunk that decolors to nothing is dropped instead of emitting a blank `[stderr]` line per progress redraw. Second, the card had no "Open Shell" link and said nothing about why. A public-review stage is forced headless even when configured onto a TUI provider (`spawnHeadless = publicReview || !isTui`), but `agent.metadata` is a hand-picked projection that carried no trace of that posture, so the card was indistinguishable from one whose PTY failed to attach. `publicReviewPosture` is now projected and the card shows a "No shell" chip explaining it. Scoped to that case only: an ordinary headless CLI agent gets no chip, and neither does a TUI run still waiting on its session id — every healthy TUI spawn passes through that state, so a chip there would put the noise straight back. The prompt size that made the run slow in the first place is #6117.
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.
Summary
A public-review Stage 3 agent looked stuck. It wasn't — the OpenCode child was nine minutes into prefilling a large prompt on a local 27B model. The card gave no way to tell, for two reasons, both fixed here.
opencode runwraps its status line in bare ANSI resets. Those bytes reachedoutput.txtand the live tail verbatim, and the browser drops only the ESC — so the only thing on the card was[stderr] [0m. Both headless spawners now strip per stream via the sharedcreateStreamingAnsiStripper, ahead of the fallback-signal detector and the codex stderr formatter (each matches provider prose that an SGR pair would otherwise split).cos-runnergets the same fix. Its handlers are a twin ofspawnDirectly's, so the identical bug reproduced whenever a task was dispatched withuseRunner— fixing only one spawner would have left half the dispatch matrix broken.spawnHeadless = publicReview || !isTui), butagent.metadatais a hand-picked projection that carried no trace of that posture — so the card was indistinguishable from one whose PTY failed to attach.publicReviewPostureis now projected, and the card shows a "No shell" chip explaining it.Scoped deliberately: an ordinary headless CLI agent gets no chip, and neither does a TUI run still waiting on its session id — every healthy TUI spawn passes through that state, so a chip there would put the "looks wedged" noise straight back.
A stream-json stdout is NDJSON with no raw ESC, so the busiest stdout stream skips the scan. A chunk that decolors to nothing is dropped rather than emitting a blank
[stderr]line per progress redraw; a whitespace-only stdout chunk is kept, since a blank line is legitimate formatting when it isn't wearing an[stderr]tag.The prompt size that made the run slow in the first place is filed separately as #6117.
Test plan
server/services/agentCliSpawning.test.js— 5 new behavioral tests against the real handlers: escapes stripped from both streams, a sequence split across two chunks reassembled, a control-only chunk dropped instead of tagged blank, a colors-only chunk still counted as run output (proof of liveness), and a fallback signal detected through embedded color codes.server/cos-runner/index.test.js— 4 parity tests pinning the runner twin (the module binds a socket server at load and can't be imported, matching that file's existing source-inspection convention).client/src/components/cos/tabs/AgentCard.test.jsx— 4 tests: the chip appears with its explanation for a public-review agent, stays absent for an ordinary headless CLI agent and for a TUI run mid-attach, and never displaces the real "Open Shell" link.server/services/agentLifecycle.test.js— the posture is projected onto the agent record.serverandclientsuites pass. Reviewed withagy(Gemini 3.8 Flash, medium effort): no findings.