Complete grok turns when the session/prompt response is lost - #553
Open
Adam-Dalloul wants to merge 1 commit into
Open
Complete grok turns when the session/prompt response is lost#553Adam-Dalloul wants to merge 1 commit into
Adam-Dalloul wants to merge 1 commit into
Conversation
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.
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.
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:session/newat ~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_initializedonly at ~35s: two servers fail handshake and one auth-gated server dies withworker quit with fatal: Transport channel closed, when Auth(AuthorizationRequired))._x.ai/session_notificationwithsessionUpdate: "response_completed"plus usage at ~17.7s.session/promptnever arrives. Nothing else does either. The tap stops atmcp_initializedand 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_completednotification.Two things differ between the healthy and stuck environments (the prompt overlapping a slow MCP init, and the
id:nullreply 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_completedarrives and the response does not.The turn loop in
connection.rsexits only on the prompt response or aStopReasonsession message, andmap_grok_ext_notificationdeliberately declines turn-level outcomes, so a turn like this never breaks the loop: noTurnComplete, the conversation row staysin_progress, and the delegation reportsrunningforever. 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_completedfor the active session now arms a 10s grace timer in the turn loop. On a healthy turn the real response lands milliseconds later, wins theselect!, and nothing changes. If the response never arrives, the timer completes the turn from the notification through the same shape as theStopReason-message exit, including deliberately not callingrecord_turn_end, since the response_metathat exit stands in for never existed. Grok-gated, session-id checked, armed once per turn.Validation
cargo checkforcodeg(desktop),codeg-serverandcodeg-mcp: clean.cargo clippy --all-targets --features test-utils -- -D warnings: clean.model_changedsibling update that must not arm the timer): pass.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 logsreceived message with neither id nor methodfor 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.