From 43a572f3f7e87dbab30640577081921eba88b676 Mon Sep 17 00:00:00 2001 From: "[._.]/ Adam Eivy" Date: Thu, 3 Sep 2026 20:46:22 +0000 Subject: [PATCH] strip ANSI from headless agent output so a slow run stops looking wedged MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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. --- client/src/components/cos/tabs/AgentCard.jsx | 18 ++++ .../components/cos/tabs/AgentCard.test.jsx | 48 +++++++++ server/cos-runner/index.js | 25 ++++- server/cos-runner/index.test.js | 37 +++++++ server/services/agentCliSpawning.js | 35 +++++- server/services/agentCliSpawning.test.js | 101 ++++++++++++++++++ server/services/agentLifecycle.js | 7 ++ server/services/agentLifecycle.test.js | 10 ++ 8 files changed, 272 insertions(+), 9 deletions(-) diff --git a/client/src/components/cos/tabs/AgentCard.jsx b/client/src/components/cos/tabs/AgentCard.jsx index 4ee8eb15ff..50cf738c58 100644 --- a/client/src/components/cos/tabs/AgentCard.jsx +++ b/client/src/components/cos/tabs/AgentCard.jsx @@ -329,6 +329,15 @@ export default function AgentCard({ agent, onPause, onKill, onDelete, onResume, ), [inactive, fullOutput, liveOutput, agent.output]); const lastOutput = output.length > 0 ? output[output.length - 1]?.line : null; + // Why this run has NO "Open Shell" link. Scoped to the one case where the user + // configured a TUI provider and got no shell anyway: a public-review stage is + // forced headless regardless (`spawnHeadless = publicReview || !isTui`). An + // ordinary headless CLI agent gets no chip — nobody expected a shell there, and + // one on every card would be the noise this exists to remove. + const noShellReason = !agent.metadata?.tuiSessionId && agent.metadata?.publicReviewPosture + ? 'No shell: a public-review stage always runs headless, even when you configure it onto a TUI provider — the screened PR content stays inside the sandboxed child. Watch the live output below to see what it is doing.' + : null; + // Extract recent tool activity (last few tool lines) for live display const recentActivity = useMemo(() => { if (inactive || output.length === 0) return []; @@ -616,6 +625,15 @@ export default function AgentCard({ agent, onPause, onKill, onDelete, onResume, {agent.metadata.tuiSessionId.slice(0, 6)} )} + {!inactive && noShellReason && ( + + + )} {!remote && (