fix(server): Claude's lone explanation before a question shows as text#27271
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
AskUserQuestionrow fails withexpected [ 'reasoning' ] to deeply equal [ 'assistant_message' ]; - at the fix, both rows pass;
- the
Bashrow staysreasoningon both sides.
- on main, the
- The whole
ClaudeAdapterV2.test.tspasses: 256/256. vp run check:changedpasses (format, lint, knip, typecheck, tests).- Runtime transcript: the 157 recorded live frames of that
thinking -> AskUserQuestionmessage (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 |
| 2 | msg_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 |
| 4 | msg_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_messageunder 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
Added 2 commits
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.
Thread transfer impact
✅ Thread transfer remains within every enforced ceiling.
| Provider | Metric | Main baseline | This PR | Impact | PR ceiling | |
|---|---|---|---|---|---|---|
| Codex | Total thread wire | 5.0 KiB | 5.0 KiB | −6 B (−0.1%) | 6.8 KiB | ✅ |
| Codex | Thread snapshot wire | 3.8 KiB | 3.8 KiB | 0 B (0.0%) | 4.9 KiB | ✅ |
| Codex | Live turn WebSocket wire | 1.3 KiB | 1.2 KiB | −6 B (−0.5%) | 2.0 KiB | ✅ |
| Codex | Live turn WebSocket decoded | 21.6 KiB | 21.6 KiB | 0 B (0.0%) | 24.4 KiB | ✅ |
| Codex | Live turn messages | 2 | 2 | 0 (0.0%) | 8 | ✅ |
| Claude | Total thread wire | 5.1 KiB | 5.0 KiB | −6 B (−0.1%) | 6.8 KiB | ✅ |
| Claude | Thread snapshot wire | 3.8 KiB | 3.8 KiB | 0 B (0.0%) | 4.9 KiB | ✅ |
| Claude | Live turn WebSocket wire | 1.3 KiB | 1.3 KiB | −6 B (−0.5%) | 2.0 KiB | ✅ |
| Claude | Live turn WebSocket decoded | 21.9 KiB | 21.9 KiB | 0 B (0.0%) | 24.4 KiB | ✅ |
| Claude | Live turn messages | 2 | 2 | 0 (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.
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 beforeBashremains 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.tsThe 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 beforeBashis 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:
bugfixSuggested 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 name Status Explanation Title check Passed The title clearly summarizes the main change: Claude’s lone explanation before a question now appears as text. Description check Passed The description covers the problem, change, scope and approval, and focused verification. It also states what was not checked. Linked Issues check Passed [ #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 check Passed 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 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.
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.