fix(server): Claude's lone explanation before a question shows as text#27271

open2
main
fix/issue-27238
opened11d agobyJJulius Marminge
View on GitHubTwo-way sync enabledPR last imported 8h ago

Problem

On Claude 5.1+, a reply sometimes has no text block, just one thinking block followed by an AskUserQuestion call. That thinking block is the explanation the question refers to. The adapter stored it as reasoning, so it appeared only as a collapsed Thought row, and the question card asked about context the user never saw. Since #18923, pre-tool thinking is promoted to text only when it is the second of two consecutive thinking blocks.

Reproduced on main (812c0a71c7) by driving the real Claude Agent SDK with T3's thinking options (claude-opus-5-5, effort high, thinking: { type: "adaptive", display: "summarized" }) on a synthetic "review these three PRs, then ask me which to merge" task, 6 times. In 1 of the 6, the question's message was thinking (532 chars) -> tool_use AskUserQuestion, and that single block was the review the question relied on. In the other 5, the review came as the second of two thinking blocks, which main already promotes.

Change

In ClaudeAdapterV2's assistant-snapshot loop, a lone non-empty thinking block is now promoted to assistant text when the tool call right after it is AskUserQuestion. The rest of the existing rule is unchanged: Claude 5.1+ only, the same message, and blank thinking is never promoted. Promotion reuses the same path as the second-of-two case: same item id and ordinal, and the reasoning item becomes an assistant_message. A lone thinking block before any other tool still stays reasoning.

Scope and approval

Fixes pingdotgg/t3code#27238

Triaged through the automated issue flow; GitCafe issue #27238. The issue is a triaged bug report that names this mechanism and proposes this fix, limited to AskUserQuestion so #18923's behaviour for ordinary tool calls is kept.

Verification

  • vp test run apps/server/src/orchestration-v2/Adapters/ClaudeAdapterV2.test.ts -t "lone claude-opus-5-5 thinking block":
    • on main, the AskUserQuestion row fails with expected [ 'reasoning' ] to deeply equal [ 'assistant_message' ];
    • at the fix, both rows pass;
    • the Bash row stays reasoning on both sides.
  • The whole ClaudeAdapterV2.test.ts passes: 256/256.
  • vp run check:changed passes (format, lint, knip, typecheck, tests).
  • Runtime transcript: the 157 recorded live frames of that thinking -> AskUserQuestion message (stream events and snapshots) were replayed through the adapter on each side, using a scratch harness test that is not committed:
1== BEFORE: adapter at main 812c0a71c7
2msg_011CgXR1p3ibXZmTjTYeK7Tk:thinking:0 reasoning: Without the repo I can only go by the notes: PR 101 is a minor parser version bump with passing checks, probably safe but can't verify behavior changes without …
3== AFTER: fix head 8785bb35f7
4msg_011CgXR1p3ibXZmTjTYeK7Tk:thinking:0 assistant_message: Without the repo I can only go by the notes: PR 101 is a minor parser version bump with passing checks, probably safe but can't verify behavior changes without …
  • No client code changes. Web, desktop and mobile already render a reasoning item that becomes an assistant_message under the same id (#18923's path), so there are no captures.
  • Not checked:
    • a live T3 session end to end, because the model picks this shape only sometimes;
    • turns already stored as reasoning: they stay reasoning.

Model and harness: Claude Opus 5.5 (claude-opus-5-5) in Claude Code, orchestrated by T3 Code. Reviewed by Codex gpt-6.1-sol and Claude Opus 5.5.

🤖 Generated with Claude Code

Checks

23 successful · 11 skipped
All checks passed
8785bb3

Added 2 commits

  • test(server): reproduce #27238 lone thinking before AskUserQuestion stays reasoning0bbfa52
  • fix(server): Claude's lone explanation before a question shows as text8785bb3
Synced from GitHub

Approvability

Verdict: Approved at 8785bb3

Macroscope's review found this PR approvable — Claude’s lone explanation immediately before a question now appears as assistant text instead of collapsed reasoning. The change retains model and message safeguards and reuses the existing presentation path.

You can add or adjust custom eligibility rules. Learn more.

Synced from GitHub

Thread transfer impact

✅ Thread transfer remains within every enforced ceiling.

ProviderMetricMain baselineThis PRImpactPR ceiling
CodexTotal thread wire5.0 KiB5.0 KiB−6 B (−0.1%)6.8 KiB✅
CodexThread snapshot wire3.8 KiB3.8 KiB0 B (0.0%)4.9 KiB✅
CodexLive turn WebSocket wire1.3 KiB1.2 KiB−6 B (−0.5%)2.0 KiB✅
CodexLive turn WebSocket decoded21.6 KiB21.6 KiB0 B (0.0%)24.4 KiB✅
CodexLive turn messages220 (0.0%)8✅
ClaudeTotal thread wire5.1 KiB5.0 KiB−6 B (−0.1%)6.8 KiB✅
ClaudeThread snapshot wire3.8 KiB3.8 KiB0 B (0.0%)4.9 KiB✅
ClaudeLive turn WebSocket wire1.3 KiB1.3 KiB−6 B (−0.5%)2.0 KiB✅
ClaudeLive turn WebSocket decoded21.9 KiB21.9 KiB0 B (0.0%)24.4 KiB✅
ClaudeLive turn messages220 (0.0%)8✅

Baseline: 812c0a7 · PR result: 8785bb3 · Source CI: success

Scenario and decoded snapshot size

10 historical turns, 5 command tools per turn, 878.9 KiB retained MCP result per historical turn, and a 1.05 MiB retained result in the measured turn.

  • Codex decoded thread snapshot: 108.5 KiB
  • Claude decoded thread snapshot: 108.8 KiB

Updated in place by a trusted workflow. PR artifacts are strictly validated and never executed.

Synced from GitHub

Review in Change Stack →

Walkthrough

Claude snapshot handling now promotes a lone pre-tool thinking block to an assistant message when the next tool is AskUserQuestion. A regression test checks this behavior and confirms that a block before Bash remains reasoning.

Changes: Claude question explanation

Layer / File(s)Summary
Thinking block promotion and regression coverage
apps/server/src/orchestration-v2/Adapters/ClaudeAdapterV2.ts, apps/server/src/orchestration-v2/Adapters/ClaudeAdapterV2.test.ts
The adapter emits the preceding block as an assistant message before AskUserQuestion, even when it does not follow another thinking block. The test checks this case and confirms that a block before Bash is represented as reasoning.

Priority: External image Normal

Estimated code review effort: 2 (Simple) | ~10 minutes

Change: Bug fix · Severity of issue fixed: Medium

Suggested labels: bugfix

Suggested reviewers: t3dotgg, macroscopeapp

Merge Risk: External image Minimal · up to 8785b

The change makes question explanations visible as messages. No merge-blocking issue was established.

Pre-merge checks: Passed 4
Check nameStatusExplanation
Title checkPassedThe title clearly summarizes the main change: Claude’s lone explanation before a question now appears as text.
Description checkPassedThe description covers the problem, change, scope and approval, and focused verification. It also states what was not checked.
Linked Issues checkPassed[#27238] requires a non-empty lone thinking block before AskUserQuestion to appear as assistant text on Claude 5.1+, while preserving reasoning for ordinary tools. The adapter extends the existing sam…
Out of Scope Changes checkPassedThe changes are limited to the Claude adapter promotion condition, its comment, and a regression test for [#27238]. The Bash test case verifies that behavior for ordinary tools remains unchanged. No u…
Full details: Linked Issues check

Explanation

[#27238] requires a non-empty lone thinking block before AskUserQuestion to appear as assistant text on Claude 5.1+, while preserving reasoning for ordinary tools. The adapter extends the existing same-message promotion path for AskUserQuestion. The regression test checks the single-thinking-block case and confirms AskUserQuestion produces one assistant_message while Bash remains reasoning. The reported full test and changed-check runs support the implementation.

Full details: Out of Scope Changes check

Explanation

The changes are limited to the Claude adapter promotion condition, its comment, and a regression test for [#27238]. The Bash test case verifies that behavior for ordinary tools remains unchanged. No unrelated changes appear in the whole-PR diff.

Finishing Touches
📝 Generate docstrings
  • Commit to this branch
  • Create a new PR
🧪 Generate unit tests (beta)
  • Commit to this branch
  • Create a new PR
  • Autofix · Keep fixing CodeRabbit findings and required CI, and resolving merge conflicts

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

Synced from GitHub

I filed #27238. The code path this PR changes is the same at v0.1.6 and at main 3ee5515. In ClaudeAdapterV2.ts, a thinking block only becomes assistant text when it directly follows another thinking block in the same message (v0.1.6 :1772-1778 and :8410-8451). So a single pre-question thinking block still shows as a collapsed "Thought", and the user answers AskUserQuestion without reading it. I can't reproduce it on demand, because the model decides when it writes thinking.

This PR has carried pr:gate-failed since 2026-10-30. Every check that ran passed, and no comment says which gate failed. Which gate is it, and can I provide or test anything to move it along?

Drafted with Claude Opus 5.5 (claude-opus-5-5) in Claude Code, via t3 triage.