Skip to content

Commit 44b39fe

Browse files
authored
fix(server): key AskUserQuestion answers by question text (#2404)
1 parent 02903f2 commit 44b39fe

2 files changed

Lines changed: 36 additions & 1 deletion

File tree

apps/server/src/provider/Layers/ClaudeAdapter.test.ts

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3296,6 +3296,9 @@ describe("ClaudeAdapterLive", () => {
32963296
assert.equal(typeof requestId, "string");
32973297
assert.equal(requestedEvent.value.payload.questions.length, 1);
32983298
assert.equal(requestedEvent.value.payload.questions[0]?.question, "Which framework?");
3299+
// Regression for #2388: `id` must equal the full question text so the
3300+
// UI's draft-answer key matches what the SDK looks up downstream.
3301+
assert.equal(requestedEvent.value.payload.questions[0]?.id, "Which framework?");
32993302
assert.deepEqual(requestedEvent.value.providerRefs, {
33003303
providerItemId: ProviderItemId.make("tool-ask-1"),
33013304
});
@@ -3330,6 +3333,34 @@ describe("ClaudeAdapterLive", () => {
33303333
assert.deepEqual(updatedInput.answers, { "Which framework?": "React" });
33313334
// Original questions should be passed through.
33323335
assert.deepEqual(updatedInput.questions, askInput.questions);
3336+
3337+
// Compatibility check for #2388: the answers shape we hand to the SDK
3338+
// must produce a non-empty rendered tool_result on BOTH SDK iteration
3339+
// patterns we have seen, so we don't regress the issue and we don't
3340+
// break users still on the older Claude CLI.
3341+
const sdkAnswers = updatedInput.answers as Record<string, unknown>;
3342+
const sdkQuestions = updatedInput.questions as ReadonlyArray<{
3343+
readonly question: string;
3344+
}>;
3345+
3346+
// Claude CLI 2.1.119 — key-agnostic Object.entries iteration. Any key
3347+
// works here, but it must at least round-trip into a non-empty string.
3348+
const v119Rendered = Object.entries(sdkAnswers)
3349+
.map(([key, value]) => `"${key}"="${String(value)}"`)
3350+
.join(", ");
3351+
assert.equal(v119Rendered, '"Which framework?"="React"');
3352+
3353+
// Claude CLI 2.1.121 — lookup by full question text. This is the path
3354+
// that regressed in #2388 when the answers were keyed by `header`.
3355+
const v121Rendered = sdkQuestions
3356+
.map(({ question }) => {
3357+
const answer = sdkAnswers[question];
3358+
return answer === undefined ? null : `"${question}"="${String(answer)}"`;
3359+
})
3360+
.filter((entry): entry is string => entry !== null)
3361+
.join(", ");
3362+
assert.notEqual(v121Rendered, "", "Expected non-empty SDK 2.1.121 tool_result (#2388)");
3363+
assert.equal(v121Rendered, '"Which framework?"="React"');
33333364
}).pipe(
33343365
Effect.provideService(Random.Random, makeDeterministicRandomService()),
33353366
Effect.provide(harness.layer),

apps/server/src/provider/Layers/ClaudeAdapter.ts

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2563,10 +2563,14 @@ export const makeClaudeAdapter = Effect.fn("makeClaudeAdapter")(function* (
25632563
const requestId = ApprovalRequestId.make(yield* Random.nextUUIDv4);
25642564

25652565
// Parse questions from the SDK's AskUserQuestion input.
2566+
// `id` MUST equal the full question text — Claude SDK >= 2.1.121 looks
2567+
// up answers by question text in `mapToolResultToToolResultBlockParam`,
2568+
// so the key the UI uses to keep its draft answer must match the SDK's
2569+
// expected lookup key. See https://github.com/pingdotgg/t3code/issues/2388
25662570
const rawQuestions = Array.isArray(toolInput.questions) ? toolInput.questions : [];
25672571
const questions: Array<UserInputQuestion> = rawQuestions.map(
25682572
(q: Record<string, unknown>, idx: number) => ({
2569-
id: typeof q.header === "string" ? q.header : `q-${idx}`,
2573+
id: typeof q.question === "string" && q.question.length > 0 ? q.question : `q-${idx}`,
25702574
header: typeof q.header === "string" ? q.header : `Question ${idx + 1}`,
25712575
question: typeof q.question === "string" ? q.question : "",
25722576
options: Array.isArray(q.options)

0 commit comments

Comments
 (0)