Skip to content

fix(server): key AskUserQuestion answers by question text#2404

Merged
juliusmarminge merged 2 commits intopingdotgg:mainfrom
basmilius:fix/ask-user-question-answer-keys
Apr 30, 2026
Merged

fix(server): key AskUserQuestion answers by question text#2404
juliusmarminge merged 2 commits intopingdotgg:mainfrom
basmilius:fix/ask-user-question-answer-keys

Conversation

@basmilius
Copy link
Copy Markdown
Contributor

@basmilius basmilius commented Apr 29, 2026

What Changed

UserInputQuestion.id (parsed in ClaudeAdapter.handleAskUserQuestion) is now derived from the full question text instead of header. The web UI keys its draft answers by question.id, so the answers record returned to the SDK in updatedInput.answers is now keyed by question text, exactly what Claude SDK ≥ 2.1.121 looks up. header stays UI-display only.

A regression assertion in the existing "handles AskUserQuestion via user-input.requested/resolved lifecycle" test renders the adapter's updatedInput.answers through both SDK iteration patterns (2.1.119's key-agnostic Object.entries and 2.1.121's answers[question] lookup) and asserts both produce a non-empty tool_result, locking the keying contract on either side of the regression.

Why

Fixes #2388.

Between Claude CLI 2.1.119 and 2.1.121 the SDK's mapToolResultToToolResultBlockParam for AskUserQuestion switched from a key-agnostic Object.entries(answers) map to a lookup by full question text:

// 2.1.121
questions.map(({ question }) => {
  const a = answers[question];
  if (!a && !annotations?.[question]?.notes) return null;
  return `"${question}"="${a}"`;
}).filter(x => x !== null).join(", ")

T3 was submitting answers keyed by header (e.g. "Approach"), not by the full question text (e.g. "Which approach do you prefer?"). Under 2.1.121 every lookup missed, every entry was filtered out, and the model received an empty interpolation:

User has answered your questions: . You can now continue with the user's answers in mind.

Keying by question text is also what the documented outputSchema shape calls for, so this aligns T3 with the spec rather than relying on the older SDK's lax behavior. The change is backwards compatible with 2.1.119, its Object.entries iteration accepts any key, so older CLI users keep working (and now see semantically richer rendered answers, e.g. "Which approach?"="Option A" instead of "Approach"="Option A").

id in UserInputQuestion is TrimmedNonEmptyStringSchema with no length cap and is only used as a Record<string, …> key in pendingUserInput.ts and session-logic.ts, never as a URL/route key, so widening it to the full question text is safe.

Checklist

  • This PR is small and focused
  • I explained what changed and why
  • I included before/after screenshots for any UI changes
  • I included a video for animation/interaction changes

Note

Key AskUserQuestion answers by full question text instead of header

Macroscope summarized 1d6d905.

Claude SDK >= 2.1.121 changed `mapToolResultToToolResultBlockParam`
to look up `AskUserQuestion` answers by the full question text. T3
was setting `UserInputQuestion.id` to `header`, so the UI's draft
key (and therefore the answers record returned to the SDK) was the
header instead of the question text. Every lookup missed and the
model received an empty interpolation in `tool_result`.

Use the full question text as the parsed `id` so the entire chain
(UI drafts -> server -> SDK) keys answers consistently with what
the SDK now expects.

Fixes pingdotgg#2388
…1.121

Render the adapter's `updatedInput.answers` payload through both Claude
SDK iteration patterns we have observed in the wild:

- 2.1.119 used a key-agnostic `Object.entries(answers)` map, so any key
  produced a non-empty `tool_result`.
- 2.1.121 looks up `answers[question]` by full question text — keys that
  don't match are dropped, which is what caused pingdotgg#2388.

Asserting both renderings stay non-empty locks in the keying contract
on either side of the regression so we don't quietly break older CLIs
or re-introduce the original bug.

Refs pingdotgg#2388
@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented Apr 29, 2026

Important

Review skipped

Auto reviews are disabled on this repository. Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro

Run ID: 381aa7fc-35ea-403b-9e20-e8b3ee1144d8

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@github-actions github-actions Bot added size:XS 0-9 changed lines (additions + deletions). vouch:unvouched PR author is not yet trusted in the VOUCHED list. labels Apr 29, 2026
@macroscopeapp
Copy link
Copy Markdown
Contributor

macroscopeapp Bot commented Apr 29, 2026

Approvability

Verdict: Approved

Straightforward bug fix that changes answer key lookup from header to question text to match Claude SDK 2.1.121 expectations. Single line production change with comprehensive regression tests documenting compatibility with multiple SDK versions.

You can customize Macroscope's approvability policy. Learn more.

@juliusmarminge juliusmarminge merged commit 44b39fe into pingdotgg:main Apr 30, 2026
12 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

size:XS 0-9 changed lines (additions + deletions). vouch:unvouched PR author is not yet trusted in the VOUCHED list.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Bug]: AskUserQuestion answers dropped after Claude CLI 2.1.121 — model receives empty answer payload

3 participants