fix(server): Claude MCP timeouts preserve the native session#25660

merged4
main
fix/issue-22198
opened1d agobyJJulius Marminge
View on GitHubTwo-way sync enabledPR last imported 16h ago

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 1 native session generations. With the fix, all three refusal cases pass. Raw red output, raw green output.
  • ProviderSwitch.integration.test.ts and ProviderTurnStartService.test.ts: 66 tests passed.
  • After merging current main, vp run check:changed passed 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. No uncertain_history_delivery fallback occurred.
Before — main dd5b5401e1After — 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

21 successful · 18 skipped
All checks passed
384c16d

Added 4 commits

  • test(server): reproduce issue 22198 MCP registration timeout dropping the Claude session03f9386
  • Merge remote-tracking branch 'origin/main' into fix/issue-22198384c16d
  • fix(server): Claude MCP registration timeouts keep the native session44a07ec
  • Merge main into fix/issue-221988f657bf
Synced from GitHub

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.

Synced from GitHub

Review in Change Stack →

No actionable comments were generated in the recent review.

External image Recent review info
External image Run configuration
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.ts
  • apps/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.ts
The refusal tag list includes ClaudeMcpRegistrationTimeoutError. The integration test simulates this error during setMcpServers and 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 setMcpServers registration timeout and a provider-start failure. This change classifies that timeout as a pre-prompt refusal.

Possibly related PRs

Suggested labels: bugfix

Suggested reviewers: only21mil

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 nameStatusExplanation
Title checkPassedThe title clearly and concisely describes the main change: preserving the native session after Claude MCP timeouts.
Description checkPassedThe 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 checkPassedIssue #22198 requires the MCP registration timeout to be treated as a refusal when Claude has not received the prompt. The PR adds ClaudeMcpRegistrationTimeoutError to PROMPT_REFUSAL_TAGS in `Prov…
Out of Scope Changes checkPassedThe 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 #22198 requires the MCP registration timeout to be treated as a refusal when Claude has not received the prompt. The PR adds ClaudeMcpRegistrationTimeoutError to PROMPT_REFUSAL_TAGS in ProviderTurnStartService.ts. The existing recursive cause check therefore restores unsent handoff bookkeeping for the wrapped ClaudeAgentSdkQueryRunnerError. ProviderSwitch.integration.test.ts adds 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.

Synced from GitHub

Thread transfer impact

✅ Thread transfer remains within every enforced ceiling.

ProviderMetricMain baselineThis PRImpactPR ceiling
CodexTotal thread wire5.0 KiB5.0 KiB0 B (0.0%)6.8 KiB✅
CodexThread snapshot wire3.8 KiB3.8 KiB0 B (0.0%)4.9 KiB✅
CodexLive turn WebSocket wire1.2 KiB1.2 KiB0 B (0.0%)2.0 KiB✅
CodexLive turn WebSocket decoded20.9 KiB20.9 KiB0 B (0.0%)29.3 KiB✅
CodexLive turn messages220 (0.0%)8✅
ClaudeTotal thread wire5.0 KiB5.0 KiB0 B (0.0%)6.8 KiB✅
ClaudeThread snapshot wire3.8 KiB3.8 KiB0 B (0.0%)4.9 KiB✅
ClaudeLive turn WebSocket wire1.2 KiB1.2 KiB0 B (0.0%)2.0 KiB✅
ClaudeLive turn WebSocket decoded21.2 KiB21.2 KiB0 B (0.0%)29.3 KiB✅
ClaudeLive turn messages220 (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.