{"record":{"id":"5591fe968417ec05","repo":"xai-org/grok-build","slug":"user-question-session-ended-unexpectedly-client-m","errorCode":null,"errorMessage":"User question session ended unexpectedly (client may have disconnected)","messagePattern":"User question session ended unexpectedly \\(client may have disconnected\\)","errorType":"error_code","errorClass":"ToolError","httpStatus":null,"severity":"error","filePath":"crates/codegen/xai-grok-tools/src/implementations/grok_build/ask_user_question/mod.rs","lineNumber":465,"sourceCode":"            question_count,\n            timeout_secs = ?wait.map(|d| d.as_secs()),\n            \"Asked user questions, blocking for response\"\n        );\n\n        // ── Step 6: Block on the oneshot result (whole batch, one timer) ─\n        // A single pending-decision timeout covers the questionnaire, not per\n        // question: N questions in one call share one wait.\n        // A `None` budget (`timeout_enabled = false`) runs the same await with\n        // no timer, normalized into the timed shape so one match handles both.\n        let outcome = match wait {\n            Some(dur) => tokio::time::timeout(dur, result_rx).await,\n            None => Ok(result_rx.await),\n        };\n        let result = match outcome {\n            Ok(Ok(r)) => r,\n            Ok(Err(_recv_error)) => {\n                return Err(xai_tool_runtime::ToolError::execution(\n                    xai_tool_protocol::ToolId::new(\"ask_user_question\").expect(\"valid\"),\n                    \"User question session ended unexpectedly (client may have disconnected)\",\n                ));\n            }\n            Err(_elapsed) => {\n                tracing::info!(\n                    question_count,\n                    timeout_secs = ?wait.map(|d| d.as_secs()),\n                    \"User question timed out; continuing without answers\"\n                );\n                // Drop the oneshot receiver on return. The shell coordinator\n                // races `result_tx.closed()` against ACP so it unblocks and\n                // can open the next questionnaire (stale UI is cancelled when\n                // a new ext_method arrives). Same model text as cancel.\n                return Ok(AskUserQuestionOutput::UserAnswered {\n                    message: unanswered.to_string(),\n                });\n            }\n        };","sourceCodeStart":447,"sourceCodeEnd":483,"githubUrl":"https://github.com/xai-org/grok-build/blob/bc7f02eddd3d84085849dc19ed216f11c23b0571/crates/codegen/xai-grok-tools/src/implementations/grok_build/ask_user_question/mod.rs#L447-L483","documentation":"The ask_user_question tool sends a questionnaire to the client via a channel and blocks on a tokio oneshot receiver for the answers. This error is returned when the oneshot sender was dropped without ever sending a response (`RecvError`), meaning the session that was supposed to answer the question went away. The library throws it because the tool cannot produce a meaningful result once the answering side is gone.","triggerScenarios":"The coordinator/client holding `result_tx` is dropped before replying — e.g. the ACP client disconnects mid-questionnaire, the shell coordinator task is cancelled/panics, or the user closes the session while questions are pending. Distinguishable from a timeout: the wait budget did not elapse; the channel closed early.","commonSituations":"User closes the editor/CLI window while a question is pending; IDE extension host restarts during an agent run; the client process crashes or is killed; network drop tears down the ACP session; running the tool headlessly where no interactive client exists.","solutions":["Verify the client (ACP peer) is still connected and alive before invoking ask_user_question; reconnect the session and re-run the agent step.","Check shell logs around the failure for coordinator/client disconnect or panic messages to identify why result_tx was dropped.","Retry the tool call after restoring the session; the error is transient if the disconnect was temporary.","If running non-interactively, configure non_interactive/wait budget params so the tool degrades to an unanswered result instead of blocking on a channel."],"exampleFix":"// before: calling the tool from a script with no live client\nawait session.prompt(\"ask_user_question\", { questions });\n// after: guard on client connectivity / use non-interactive mode\nif (!session.clientConnected()) {\n  params.set(\"ask_user_question\", { non_interactive: true });\n}\nawait session.prompt(\"ask_user_question\", { questions });","handlingStrategy":"try-catch","validationCode":"// before calling, ensure an interactive client is attached\nif (!session.clientConnected || !session.clientConnected()) {\n  params.set(\"ask_user_question\", { non_interactive: true });\n}","typeGuard":"fn has_live_client(res: &Resources) -> bool {\n    res.get::<NotificationHandle>().is_some()\n}","tryCatchPattern":"try {\n  const out = await tool(\"ask_user_question\", { questions });\n} catch (e) {\n  if (String(e.message).includes(\"session ended unexpectedly\")) {\n    await reconnect(); // or fall back to non-interactive defaults\n  } else { throw e; }\n}","preventionTips":["Only call ask_user_question when a live interactive client is attached","Use non_interactive mode for headless/CI runs","Monitor client disconnect events and cancel pending questionnaires promptly","Catch this error and fall back to sensible defaults instead of failing the whole run"],"tags":["channel-closed","client-disconnect","tokio-oneshot","interactive-session"],"backgroundTag":"oneshot-channel-closed","analyzedSha":"bc7f02eddd3d84085849dc19ed216f11c23b0571","analyzedAt":"2026-08-31T04:59:42.031Z","schemaVersion":2},"datasetVersion":"2026-08-31T09:17:48.483Z"}