Skip to content

[Bug]: Claude AskUserQuestion with a blank option description fails the run and wedges the thread #15316

Description

@tarik02

Before submitting

  • I searched existing issues and did not find a duplicate.
  • I included enough detail to reproduce or investigate the problem.

Area

apps/server

Steps to reproduce

  1. On a Claude thread (orchestration V2, claudeAgent), get Claude to call AskUserQuestion where at least one option has description: "" or no description. Claude does this regularly for short trailing options like "No".
  2. Watch the run.

Expected behavior

The question card appears and the run waits for the answer.

Actual behavior

  • The run is marked failed about 30 ms after the tool call, with the generic "Provider turn failed.".
  • The question card never appears. The pending request is listed but can't be read or answered, and the UI keeps showing "Waiting on background task AskUserQuestion".
  • The Claude SDK turn stays blocked in canUseTool. Every next message fails with ProviderAdapterProtocolError: Claude provider turn … is still active until the Claude CLI process exits.

Root cause: claudeUserInputQuestions (apps/server/src/orchestration-v2/Adapters/ClaudeAdapterV2.ts) maps a missing or blank option description to "". OrchestrationV2UserInputQuestion requires a non-empty description, so the user_input_request turn item fails to decode in ProviderEventIngestor. RunExecutionService then fails the run without interrupting the provider turn. The Codex and OpenCode adapters already fall back to the option label.

Pi's extension select dialog 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.

Activity

  1. juliusmarminge commented on Oct 3, 2026

    @juliusmarminge
    Member

    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

    claudeUserInputQuestions in apps/server/src/orchestration-v2/Adapters/ClaudeAdapterV2.ts keeps any non-empty label and sets description to the trimmed string, or to "" when the field is missing or isn't a string. OrchestrationV2UserInputQuestion in packages/contracts/src/orchestrationV2.ts types that field as TrimmedNonEmptyString, so both "" and whitespace-only text fail Schema.isNonEmpty().

    ProviderEventIngestor.makeDomainEvent decodes the turn_item.updated event before writing it, and that decode error fails the run's event consumer. RunExecutionService's catchCause logs orchestration V2 provider event ingestion failed and writes a failed terminal through makeProviderFailure. causeMessage doesn't recognize schema errors, so the thread just shows the generic "Provider turn failed."

    That write doesn't interrupt the provider session. canUseTool is still waiting inside awaitClaudeUserInputAnswers, and activeTurn stays set. The next message hits startTurn's guard and fails with ProviderAdapterProtocolError: Claude provider turn … is still active until the query process exits and finalizeActiveTurnAfterQueryExit clears it, which is why killing that process works around it.

    The question card never appears because the questions only live on the user_input_request turn 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 extension select dialog doesn't: piQuestion uses label: option || "Empty value" but description: option, so an empty-string option still fails. Grok's extractXAiAskUserQuestions uses option.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 claudeUserInputQuestions and Pi's select mapping (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.

  2. added
    bugSomething is broken or behaving incorrectly.
    via-triageFiled through npx t3 triage
    on Oct 3, 2026
  3. darjss commented on Oct 7, 2026

    @darjss
    Contributor

    The same wedge happens with ACP providers (AcpAdapterV2), so a fix in RunExecutionService (#14856) would cover more than Claude.

    What I checked on main (bfec2387b8). I started an ACP turn whose prompt never returns, then called startTurn twice more on the same runtime. Both calls failed with ProviderAdapterTurnStartError, which wraps ACP provider turn … is still active (AcpAdapterV2.ts:6826). The only place activeTurn is cleared is finalizeTurn (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 enum value to { label: value, description: value } (AcpAdapterV2.ts:5844). An empty or whitespace-only enum value would produce the same blank label/description that fails OrchestrationV2UserInputQuestion here. The agent would then stay blocked on elicitation/create while 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.

  4. Ieruss commented on Oct 8, 2026

    @Ieruss

    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 its prompt.submit hook with three options. None of them has a description. The SDK delivers this as an AskUserQuestion canUseTool request with every option's description: "":

    {"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 AskUserQuestion tool row with the raw JSON input. In the projection this is a dynamic_tool turn item, and no user_input_request item was written.
    • The user_input runtime request and the user_input_request node stay pending/waiting. t3_pending_request_list still 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.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething is broken or behaving incorrectly.via-triageFiled through npx t3 triage

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions