Repository navigation
[Bug]: Claude AskUserQuestion with a blank option description fails the run and wedges the thread #15316
Description
Activity
Note
Grok responding on behalf of Julius.
Triage
Thanks for the clear writeup and root cause, @tarik02! I can confirm this on current
main(39efcd8558). A blank option description is enough to fail the run and leave the Claude session stuck.What fails
claudeUserInputQuestionsinapps/server/src/orchestration-v2/Adapters/ClaudeAdapterV2.tskeeps any non-empty label and setsdescriptionto the trimmed string, or to""when the field is missing or isn't a string.OrchestrationV2UserInputQuestioninpackages/contracts/src/orchestrationV2.tstypes that field asTrimmedNonEmptyString, so both""and whitespace-only text failSchema.isNonEmpty().ProviderEventIngestor.makeDomainEventdecodes theturn_item.updatedevent before writing it, and that decode error fails the run's event consumer.RunExecutionService'scatchCauselogsorchestration V2 provider event ingestion failedand writes a failed terminal throughmakeProviderFailure.causeMessagedoesn't recognize schema errors, so the thread just shows the generic "Provider turn failed."That write doesn't interrupt the provider session.
canUseToolis still waiting insideawaitClaudeUserInputAnswers, andactiveTurnstays set. The next message hitsstartTurn's guard and fails withProviderAdapterProtocolError: Claude provider turn … is still activeuntil the query process exits andfinalizeActiveTurnAfterQueryExitclears it, which is why killing that process works around it.The question card never appears because the questions only live on the
user_input_requestturn item, and that's the event that fails to decode. The node and runtime request emitted just before it can still land, so a pending request can show up with nothing to answer. "Waiting on background task AskUserQuestion" is the composer strip for the still-open Claude task (presentPendingBackgroundWork).Other adapters
Codex (
nonEmptyText(option.description, option.label)) and both OpenCode adapters already fall back to the label. Pi's extensionselectdialog doesn't:piQuestionuseslabel: option || "Empty value"butdescription: option, so an empty-string option still fails. Grok'sextractXAiAskUserQuestionsusesoption.description ?? option.label, which covers a missing description but still passes an explicit""through.Likely fix area
- One option is to fall back to the option label wherever a question option is built, at least in
claudeUserInputQuestionsand Pi'sselectmapping (and possibly the Grok path for explicit""). That matches the adapters that already decode cleanly, and it lets the card show and the run wait. - Separately, interrupting the provider when ingestion fails would unstick the thread, though on its own it would still drop the question.
The open PR #15317 proposes the label fallback for Claude and Pi, with decode tests. A maintainer will decide on the fix direction.
- One option is to fall back to the option label wherever a question option is built, at least in
- addedbugSomething is broken or behaving incorrectly.Something is broken or behaving incorrectly.via-triageFiled through npx t3 triageFiled through npx t3 triage
on Oct 3, 2026 The same wedge happens with ACP providers (
AcpAdapterV2), so a fix inRunExecutionService(#14856) would cover more than Claude.What I checked on
main(bfec2387b8). I started an ACP turn whose prompt never returns, then calledstartTurntwice more on the same runtime. Both calls failed withProviderAdapterTurnStartError, which wrapsACP provider turn … is still active(AcpAdapterV2.ts:6826). The only placeactiveTurnis cleared isfinalizeTurn(AcpAdapterV2.ts:6660), and that never runs once the orchestrator has failed the run without interrupting the provider turn. The thread stays stuck until the orphaned prompt ends or the server restarts.A likely ACP trigger, not reproduced end to end. Form elicitations map each
enumvalue to{ label: value, description: value }(AcpAdapterV2.ts:5844). An empty or whitespace-only enum value would produce the same blanklabel/descriptionthat failsOrchestrationV2UserInputQuestionhere. The agent would then stay blocked onelicitation/createwhile the run is marked failed. I have only read this path in the code and haven't seen it from a real agent. Filtering blank enum values, or falling back to a default label as the Codex and OpenCode adapters do, would close it.Model/harness: Claude Opus 5.5 via Claude Code.
Another trigger for the same bug: a Claude Code plugin dialog. It reproduces on
0.0.46-nightly.20261008.2813(macOS desktop, Claude provider, orchestration V2).The prompt-coach plugin calls
$.ui.ask(...)in itsprompt.submithook with three options. None of them has a description. The SDK delivers this as anAskUserQuestioncanUseToolrequest with every option'sdescription: "":{"questions":[{"question":"Prompt coach: …\n\nWhich should I send?","header":"Coach","options":[{"label":"Send improved","description":""},{"label":"Send mine","description":""},{"label":"Edit improved","description":""}],"multiSelect":false}]}Same outcome as described above:
- No question card. The timeline only shows the
AskUserQuestiontool row with the raw JSON input. In the projection this is adynamic_toolturn item, and nouser_input_requestitem was written. - The
user_inputruntime request and theuser_input_requestnode staypending/waiting.t3_pending_request_liststill lists all three requests from this thread, although their runs are interrupted. - The turn ends with "Provider turn failed."
Because the plugin runs on every prompt it judges vague, this hits plain user messages, not only agent questions. In my thread it fired 3 times. One of those stopped turns also led to #17133.
The label fallback in #15317 should cover this case too, since the label is always non-empty here.
- No question card. The timeline only shows the
Before submitting
Area
apps/server
Steps to reproduce
claudeAgent), get Claude to callAskUserQuestionwhere at least one option hasdescription: ""or no description. Claude does this regularly for short trailing options like "No".Expected behavior
The question card appears and the run waits for the answer.
Actual behavior
canUseTool. Every next message fails withProviderAdapterProtocolError: Claude provider turn … is still activeuntil the Claude CLI process exits.Root cause:
claudeUserInputQuestions(apps/server/src/orchestration-v2/Adapters/ClaudeAdapterV2.ts) maps a missing or blank option description to"".OrchestrationV2UserInputQuestionrequires a non-empty description, so theuser_input_requestturn item fails to decode inProviderEventIngestor.RunExecutionServicethen fails the run without interrupting the provider turn. The Codex and OpenCode adapters already fall back to the option label.Pi's extension
selectdialog has the same gap for an empty-string option: its label becomes "Empty value", but its description stays"".Impact
Blocks work completely
Version or commit
main @ 429c625
Environment
Linux, headless server, Claude provider (claude-agent-sdk), orchestration V2
Logs or stack traces
WARN orchestration V2 provider event ingestion failed {runId: …ordinal:8} # decoding the stored AskUserQuestion input through claudeUserInputQuestions + OrchestrationV2UserInputQuestion: SchemaError: Expected a value with a length of at least 1 at [2]["options"][2]["description"] # every following turn: ProviderAdapterTurnStartError: Failed to start run … cause: ProviderAdapterProtocolError: Claude provider turn provider-turn:…ordinal%253A3…attempt%3A1 is still active.Workaround
Kill the thread's Claude CLI process (or restart the server). The thread then accepts new turns.