Skip to content

Complete grok turns when the session/prompt response is lost - #553

Open
Adam-Dalloul wants to merge 1 commit into
xintaofei:mainfrom
Adam-Dalloul:fix/grok-delegation-completion
Open

Complete grok turns when the session/prompt response is lost#553
Adam-Dalloul wants to merge 1 commit into
xintaofei:mainfrom
Adam-Dalloul:fix/grok-delegation-completion

Conversation

@Adam-Dalloul

Copy link
Copy Markdown
Contributor

Fixes #551.

Root cause, measured on the wire

I interposed a transparent stdio tap between codeg and grok.exe (1.0.5) and captured a live stuck delegation child end to end:

  • codeg sends session/new at ~0.3s and the delegated prompt at ~0.7s, while grok's MCP-server init is still running (12 servers on this machine, _x.ai/mcp_initialized only at ~35s: two servers fail handshake and one auth-gated server dies with worker quit with fatal: Transport channel closed, when Auth(AuthorizationRequired)).
  • grok runs the turn anyway and streams everything: thoughts, the full final message, then _x.ai/session_notification with sessionUpdate: "response_completed" plus usage at ~17.7s.
  • The JSON-RPC response to session/prompt never arrives. Nothing else does either. The tap stops at mcp_initialized and stays silent.

A standalone control speaking ACP to the same binary with the same flags, but with a fast MCP init (~1s), gets the prompt response 2 milliseconds after the response_completed notification.

Two things differ between the healthy and stuck environments (the prompt overlapping a slow MCP init, and the id:null reply frames noted at the bottom); I did not isolate which of them makes grok drop the response. The fix keys on the observable that is identical in every stuck capture: response_completed arrives and the response does not.

The turn loop in connection.rs exits only on the prompt response or a StopReason session message, and map_grok_ext_notification deliberately declines turn-level outcomes, so a turn like this never breaks the loop: no TurnComplete, the conversation row stays in_progress, and the delegation reports running forever. That chain matches #551 exactly (codex, claude and opencode children complete; grok children never do; the stuck child's final message is fully persisted). A delegated child prompts at ~0.7s every time, which is why delegation hits this reliably while interactive tabs, where the human types after init settles, mostly survive.

Fix

response_completed for the active session now arms a 10s grace timer in the turn loop. On a healthy turn the real response lands milliseconds later, wins the select!, and nothing changes. If the response never arrives, the timer completes the turn from the notification through the same shape as the StopReason-message exit, including deliberately not calling record_turn_end, since the response _meta that exit stands in for never existed. Grok-gated, session-id checked, armed once per turn.

Validation

  • cargo check for codeg (desktop), codeg-server and codeg-mcp: clean.
  • cargo clippy --all-targets --features test-utils -- -D warnings: clean.
  • Four new unit tests around the detector, using the exact notification frame captured from the stuck child (match, wrong session, non-grok agent, and the model_changed sibling update that must not arm the timer): pass.
  • I have not run a packaged desktop build with the patch; happy to test a nightly or adjust.

Side observation from the tap

The client side answers grok's unknown _x.ai/* notifications (models/update, announcements/update, sessions/changed, ...) with {"id": null, "error": {"code": -32601}} frames, and grok logs received message with neither id nor method for each. JSON-RPC notifications must not be answered. That behavior looks like it lives in sacp rather than this repo's code, so I left it alone here, but it seemed worth recording next to this bug since it is one of the two environmental differences named above.

grok can stream a whole turn and emit response_completed on
_x.ai/session_notification without ever answering session/prompt
(captured on the wire from a stuck delegation child; xintaofei#551). The turn
loop only exited on the prompt response or a StopReason message, so
such a turn hung forever: conversation row in_progress, delegation
running. response_completed now arms a 10s grace timer; the healthy
response wins the select unchanged, and a lost one completes the turn
from the notification, mirroring the StopReason-message exit.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Grok delegation never reaches completed: child writes its final reply but the task stays running forever (identical Codex delegation completes)

1 participant