fix(server): Claude MCP timeouts preserve the native session#25660
Problem
A Claude MCP registration timeout happens before the prompt is sent, but leaves a context handoff marked pending. After two such failed turns, the next request discards the resumable native session; a long conversation can then get stuck trying to deliver a full-thread handoff.
Change
Classify the MCP registration timeout as a refusal before the prompt, using the existing refusal list. This restores unsent handoff bookkeeping so the next turn resumes the original native session and delivers the missed requests.
Scope and approval
Fixes https://github.com/pingdotgg/t3code/issues/22198
Triaged bug via the automated issue flow. GitCafe: #22198.
Verification
- Regression added before the fix: the MCP timeout case fails with
expected 2 to equal 1native session generations. With the fix, all three refusal cases pass. Raw red output, raw green output. ProviderSwitch.integration.test.tsandProviderTurnStartService.test.ts: 66 tests passed.- After merging current main,
vp run check:changedpassed again at 8f657bfe94: format, lint, knip, GraphQL checks, affected typechecks and all 66 tests. Final check output. The reviewed patch is unchanged by the merge. - Publication refresh at 384c16d8b9: merged main, confirmed the two-file fix is unchanged, and reran
vp run check:changed; all scoped checks and 66 tests passed. - Real web dev app on Linux x64, Claude CLI 2.1.303, Opus 5.5: an external wrapper dropped only MCP registration requests. Two normal turns each reached both real 90-second registration timeouts. After removing the fault, the fixed app cleared pending delivery, successfully compacted, and answered the next message with
OK, retaining the baseline native session ID. Nouncertain_history_deliveryfallback occurred.
| Before — main dd5b5401e1 | After — fix 44a07ece34 |
|---|---|
Runtime transcript: pending delta retained; /compact replaced the native session. | Runtime transcript: pending delta restored; compaction and continuation retained the original session. |
Evidence index · reproduction driver · proof matrix.
Server-only change; the render matrix is empty. The shared server path serves web, desktop and mobile. Two independent code reviews returned MERGE; final proof audit returned SUFFICIENT. This prevents the reported timeout trigger; it does not repair native session references already replaced by older builds. The 553k-token oversized-handoff state was not recreated.
Models: GPT-6.1-Sol and Claude Opus 5.5; harnesses: Codex and Claude Code via T3 Code.
Checks
Added 4 commits
Approvability
Verdict: Approved at 8f657bf
Macroscope's review found this PR approvable — This narrowly scoped server fix classifies MCP registration timeouts like other pre-prompt refusals, restoring pending handoffs so the existing native Claude session is retained. A focused regression test covers the behavior, with no product-default, schema, infrastructure, security, billing, or authentication changes.
No code changes detected at 384c16d. Prior analysis still applies.
You can add or adjust custom eligibility rules. Learn more.
No actionable comments were generated in the recent review.
External image Recent review info
-
External image Run configuration
- Source mode: Auto
- Repository: pingdotgg/t3code
- Branch: fix/issue-22198
- File: .coderabbit.config.ts
- Commit: 384c16d
- Layers: Repository TS config
- Review profile: CHILL
- Plan: Team
- Run ID:
440780ed-829c-4604-b58e-e570d36b44d7
External image Commits
Reviewing files that changed from the base of the PR and between e2d3ded436f7b32c07aeb9bf7b4738bfef58156d and 384c16d8b994efa0b509bf8e28b8fcd2ee175de9.
External image Files selected for processing: 2
apps/server/src/orchestration-v2/ProviderTurnStartService.tsapps/server/src/orchestration-v2/testkit/ProviderSwitch.integration.test.ts
Included review availability: This review used your included allowance. Your plan provides up to 10 included reviews per hour; 4 remain after this review.
Walkthrough
The provider turn-start refusal check now recognizes Claude MCP registration timeouts, including when nested in a cause. The provider-switch integration test adds this timeout to the refused-turn cases.
Changes: Claude timeout refusal handling
Layer / File(s) Summary Recognize and test MCP registration timeouts
apps/server/src/orchestration-v2/ProviderTurnStartService.ts,apps/server/src/orchestration-v2/testkit/ProviderSwitch.integration.test.tsThe refusal tag list includes ClaudeMcpRegistrationTimeoutError. The integration test simulates this error duringsetMcpServersand includes it in the refused-turn cases.Priority: External image Normal
Estimated code review effort: 2 (Simple) | ~8 minutes
Change: Bug fix · Severity of issue fixed: Medium
Possibly related issues
- Issue 23151 reports a Claude
setMcpServersregistration timeout and a provider-start failure. This change classifies that timeout as a pre-prompt refusal.
Possibly related PRs
- pingdotgg/t3code#25372: Both changes address Claude MCP registration timeouts; this PR classifies the timeout as a pre-prompt refusal.
- pingdotgg/t3code#22864: Both changes add an error to
PROMPT_REFUSAL_TAGSinProviderTurnStartService. - pingdotgg/t3code#23202: Both changes address the Claude MCP registration-timeout condition.
Suggested labels:
bugfixSuggested reviewers:
only21mil- Issue 23151 reports a Claude
Merge Risk: External image Minimal · up to 384c1
The timeout refusal handling matches the production error path and the change is ready to merge.
Pre-merge checks: Passed 4
Check name Status Explanation Title check Passed The title clearly and concisely describes the main change: preserving the native session after Claude MCP timeouts. Description check Passed The description includes all required sections. It explains the problem, the fix, scope and approval, focused regression tests, broader verification, observed results, limitations, and tool usage. Linked Issues check Passed Issue #22198requires the MCP registration timeout to be treated as a refusal when Claude has not received the prompt. The PR addsClaudeMcpRegistrationTimeoutErrortoPROMPT_REFUSAL_TAGSin `Prov…Out of Scope Changes check Passed The diff contains one server behavior change and its regression test. Both changes directly support issue #22198. No unrelated product behavior or unrelated files appear in the reviewed diff.Full details: Linked Issues check
Explanation
Issue
#22198requires the MCP registration timeout to be treated as a refusal when Claude has not received the prompt. The PR addsClaudeMcpRegistrationTimeoutErrortoPROMPT_REFUSAL_TAGSinProviderTurnStartService.ts. The existing recursivecausecheck therefore restores unsent handoff bookkeeping for the wrappedClaudeAgentSdkQueryRunnerError.ProviderSwitch.integration.test.tsadds the timeout regression case to the native-session retention test. The broader issue suggestions are follow-up behavior, and the PR explicitly limits its objective to the reported timeout trigger.
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.
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 | 0 B (0.0%) | 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.2 KiB | 1.2 KiB | 0 B (0.0%) | 2.0 KiB | ✅ |
| Codex | Live turn WebSocket decoded | 20.9 KiB | 20.9 KiB | 0 B (0.0%) | 29.3 KiB | ✅ |
| Codex | Live turn messages | 2 | 2 | 0 (0.0%) | 8 | ✅ |
| Claude | Total thread wire | 5.0 KiB | 5.0 KiB | 0 B (0.0%) | 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.2 KiB | 1.2 KiB | 0 B (0.0%) | 2.0 KiB | ✅ |
| Claude | Live turn WebSocket decoded | 21.2 KiB | 21.2 KiB | 0 B (0.0%) | 29.3 KiB | ✅ |
| Claude | Live turn messages | 2 | 2 | 0 (0.0%) | 8 | ✅ |
Baseline: e2d3ded · PR result: 384c16d · 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.