fix(server): Grok background shell commands stay in the run that started them - #13789
Conversation
…ted them Grok acknowledges a command it runs in the background with a completed `BackgroundTaskStarted` tool update while the process keeps running, then streams its output and ends it only with `x.ai/task_completed`. The adapter only tracked monitors that way, so the start acknowledgement finished the command, the first late output frame opened a synthetic continuation run and moved the command into it, and Grok's own wake reply opened another. Track `BackgroundTaskStarted` like a monitor start: keep the command running, hold its run open until the task ends, and leave Grok's reply to the continuation run. Recorded replay fixture: grok_background_bash. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
| {"type":"emit_inbound","label":"notification:session/update:agent_thought_chunk:t3-xai-prompt-1","frame":{"kind":"notification","method":"session/update","params":{"sessionId":"00000000-0000-4000-8000-000000000001","update":{"sessionUpdate":"agent_thought_chunk","content":{"type":"text","text":" was"}},"_meta":{"totalTokens":23745,"eventId":"00000000-0000-4000-8000-000000000001-85","agentTimestampMs":1790405183378,"promptId":"t3-xai-prompt-1","streamStartMs":1790405182783,"turnStartMs":1790405167500,"updateType":"AgentThoughtChunk","chunkId":22}}}} | ||
| {"type":"emit_inbound","label":"notification:session/update:agent_thought_chunk:t3-xai-prompt-1","frame":{"kind":"notification","method":"session/update","params":{"sessionId":"00000000-0000-4000-8000-000000000001","update":{"sessionUpdate":"agent_thought_chunk","content":{"type":"text","text":" very"}},"_meta":{"totalTokens":23745,"eventId":"00000000-0000-4000-8000-000000000001-86","agentTimestampMs":1790405183378,"promptId":"t3-xai-prompt-1","streamStartMs":1790405182783,"turnStartMs":1790405167500,"updateType":"AgentThoughtChunk","chunkId":23}}}} | ||
| {"type":"emit_inbound","label":"notification:session/update:agent_thought_chunk:t3-xai-prompt-1","frame":{"kind":"notification","method":"session/update","params":{"sessionId":"00000000-0000-4000-8000-000000000001","update":{"sessionUpdate":"agent_thought_chunk","content":{"type":"text","text":" explicit"}},"_meta":{"totalTokens":23745,"eventId":"00000000-0000-4000-8000-000000000001-87","agentTimestampMs":1790405183378,"promptId":"t3-xai-prompt-1","streamStartMs":1790405182783,"turnStartMs":1790405167500,"updateType":"AgentThoughtChunk","chunkId":24}}}} | ||
| {"type":"emit_inbound","label":"notification:session/update:agent_thought_chunk:t3-xai-prompt-1","frame":{"kind":"notification","method":"session/update","params":{"sessionId":"00000000-0000-4000-8000-000000000001","update":{"sessionUpdate":"agent_thought_chunk","content":{"type":"text","text":":"}},"_meta":{"totalTokens":23745,"eventId":"00000000-0000-4000-8000-000000000001-88","agentTimestampMs":1790405183378,"promptId":"t3-xai-prompt-1","streamStartMs":1790405182783,"turnStartMs":1790405167500,"updateType":"AgentThoughtChunk","chunkId":25}}}} |
There was a problem hiding this comment.
🟡 Medium grok_background_bash/grok_transcript.ndjson:120
The replay loader rejects grok_background_bash at line 120 because the text field is malformed, so the fixture cannot execute. Replace the malformed value with a valid JSON string containing the intended colon.
🚀 Reply "fix it for me" or copy this AI Prompt for your agent:
In file @apps/server/src/orchestration-v2/testkit/fixtures/grok_background_bash/grok_transcript.ndjson around line 120:
The replay loader rejects `grok_background_bash` at line 120 because the `text` field is malformed, so the fixture cannot execute. Replace the malformed value with a valid JSON string containing the intended colon.
Thread transfer impact✅ Thread transfer remains within every enforced ceiling.
Baseline: unavailable · PR result: 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. |
ApprovabilityVerdict: Would Approve Macroscope's review found this PR approvable — This is a narrow Grok ACP bug fix that keeps background shell commands running and attached to their originating run until the provider reports completion, with focused replay coverage and no product-default or static-analysis changes. An unresolved Medium finding reports that the new replay fixture is malformed at line 120, which would prevent that regression test from executing. Not approved because:
Adjust the Minimum Blocking Severity for this repo — including turning it Off — in Settings. You can add or adjust custom eligibility rules. Learn more. |
75afe68
into
t3code/codex-turn-mapping
When Grok runs a shell command in the background, T3 shows the command as finished the moment it starts. When the command really ends, T3 moves it out of the run that started it into an empty extra run, so the thread shows an extra "Worked for" fold and the command appears in the wrong place.
Seen live on a Supervised Grok 4.7 Fast thread ("start
for i in 1..6; do echo tock-$i; sleep 10; donein the background, reply 'started'"). The projection kept the command row at its run 2 ordinal but moved itsrunIdto run 3, Grok's owntask-completed-*wake run:Why
Grok acknowledges a background shell command with a completed tool update carrying
rawOutput: { type: "BackgroundTaskStarted", task_id }while the process keeps running (crates/codegen/xai-grok-shell/src/session/acp_conversion.rs:340at f0e3be1; the bash tool returns it for bothis_backgroundand the auto-background timeout,crates/codegen/xai-grok-tools/src/implementations/grok_build/bash/mod.rs:1885). It then streams the command's output asin_progressBash frames on the same tool call and ends it only withx.ai/task_completedfor thattask_id.GrokAdapterV2already tracks monitors this way. It did not recogniseBackgroundTaskStarted, so the start acknowledgement completed the command, the root run finished without waiting for it, and the first late output frame opened a synthetic continuation run that took over the command row. Grok's real wake reply then opened another run.What changed
extractXAiMonitorTaskIdandnormalizeXAiAcpToolCallStatetreat aBackgroundTaskStartedacknowledgement like a monitor start: the task id is registered and the tool stays in progress. From there the existing monitor path holds the run open untilx.ai/task_completed, hydrates the output, and leaves Grok's reply to a single continuation run.Verification
grok_background_bash, recorded live withscripts/record-grok-acp-replay-fixture.tsagainst Grok 1.0.41 andgrok-4.7-build-fast. It asserts 2 runs, the command in run 1, every tick shown running before the end, run 1 completing after the command, and Grok's reply in the continuation run.expected [ … ] to have a length of 2 but got 3(the command lands in a synthetic run 2).vp test run src/orchestration-v2/testkit/OrchestratorReplayFixtures.integration.test.ts -t grok: 14 passed (all Grok fixtures, includinggrok_monitorandgrok_background_subagent).vp test run src/provider/acp/XAiAcpExtension.test.ts src/provider/acp/XAiBackgroundTasks.test.ts src/orchestration-v2/Adapters/GrokAdapterV2.test.ts src/orchestration-v2/Adapters/AcpAdapterV2.test.ts src/orchestration-v2/testkit/OrchestratorReplayFixtures.contract.test.ts: all passed.vp exec tsc --noEmit -p .inapps/server: no errors.knip --workspace apps/server --exports: clean.vp linton touched files: one pre-existing warning on an untouched line.Model: Claude Opus 5.5 (Claude Code)
🤖 Generated with Claude Code