Repository navigation
fix(server): forks whose source session is gone start fresh instead of failing every send - #27729
juliusmarminge wants to merge 5 commits into
Conversation
…ession is gone This test reproduces #27709. A natively forked thread whose source provider session no longer exists fails every send with "Provider turn failed to start" instead of starting a fresh native session carrying the fork's inherited history. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…e source history A native fork whose source native session no longer exists failed every send with "Provider turn failed to start", and its pending fork transfer was re-targeted at each new run, so the fork stayed wedged. On the final start attempt, a failed native fork now opens a fresh native session and carries the source thread's history through to the source run as a full-thread context handoff. It resolves the fork transfer as portable so later sends stop retrying the native fork, and clears the provider thread's forkedFrom because the fresh session inherited nothing natively. Earlier attempts still rethrow so transient fork failures keep retrying. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…atively Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
| coveredRunOrdinals: { from: 1, to: sourceRun.ordinal }, | ||
| strategy: "full_thread_summary", | ||
| runs: sourceProjection.runs, | ||
| items: (yield* projectionStore.getTurnStartHistory(sourceProjection.thread.id)).filter( |
There was a problem hiding this comment.
🟠 High orchestration-v2/ProviderTurnStartService.ts:1126
When native fork creation fails for a forked source, the fallback summary omits that source's inherited conversation, so the new thread starts without part of the history being forked. getTurnStartHistory(sourceProjection.thread.id) reads only items stored on the source thread; include its inherited context through the fork point when building this handoff.
🚀 Reply "fix it for me" or copy this AI Prompt for your agent:
In file @apps/server/src/orchestration-v2/ProviderTurnStartService.ts around line 1126:
When native fork creation fails for a forked source, the fallback summary omits that source's inherited conversation, so the new thread starts without part of the history being forked. `getTurnStartHistory(sourceProjection.thread.id)` reads only items stored on the source thread; include its inherited context through the fork point when building this handoff.
ApprovabilityVerdict: Needs Human Approval Macroscope's review found this PR requires human approval — Failed native forks now automatically continue in a fresh session using summarized source history. Human review is required for this default recovery change; the separate blocking finding reports missing inherited context for nested forks. Needs human approval because:
Adjust the Minimum Blocking Severity for this repo — including turning it Off — in Settings. You can add or adjust custom eligibility rules. Learn more. |
|
No actionable comments were generated in the recent review.
|
| Layer / File(s) | Summary |
|---|---|
Native fork fallback and validation apps/server/src/orchestration-v2/ProviderTurnStartService.ts, apps/server/src/orchestration-v2/ThreadFork.execution.test.ts |
On a non-retryable fork failure during an active attempt, startup creates a fresh native thread and records a full-thread-summary handoff as portable. The resulting provider thread clears forkedFrom, and the transfer is not marked consumed. Tests cover persistent failures and a later successful native fork. |
Priority:
Estimated code review effort: 3 (Moderate) | ~20 minutes
Change: Bug fix · Severity of issue fixed: Medium
Sequence Diagram(s)
sequenceDiagram
participant ProviderTurnStartService
participant ProviderSession
participant TransferState
ProviderTurnStartService->>ProviderSession: Attempt native fork
ProviderSession-->>ProviderTurnStartService: Return fork failure
ProviderTurnStartService->>ProviderSession: Create fresh native thread
ProviderTurnStartService->>TransferState: Record full-thread-summary handoff as portable
Possibly related issues
pingdotgg/t3code#24549— The fallback may recover from a failed native fork, but this change does not remove the reported busy-state cause.pingdotgg/t3code#27348— The fallback may recover from a failed native fork, but this change does not repair the reported missing command completion.
Suggested reviewers: maria-rcks, t3dotgg, macroscopeapp
Merge Risk: 
Low · up to 02e61
-
Recovery from a missing native session can give a nested fork incomplete context. The impact is limited to that case, so the PR is mergeable with owner awareness.
Pre-merge checks: 
3 · 
1
-
Failed checks: 1 warning Passed checks: 3 passed-
Full details: Description check
-
Explanation
The description includes complete Problem, Change, Scope and approval, and Verification sections. It provides reproduction steps, implementation details, linked issue context, focused test results, runtime validation, limitations, and agent information.
Full details: Out of Scope Changes check
-
Explanation
The changed service logic directly implements [
#27709] recovery for a missing native fork session. The added tests verify source-history handoff, transfer resolution, clearedforkedFrom, and preservation of native retries. No unrelated change is established.
-
Full details: Linked Issues check
-
Explanation
The implementation satisfies the recovery requirement in [
#27709]. It retriesforkThreadwhilewillRetryis true. On the final attempt, it creates a fresh native session, prepares afull_thread_summaryhandoff from the source history, resolves the transfer asresolved_portable/portable_context, and clearsforkedFrom. The focused tests cover source-history recovery and preservation of native retries. However, [#27709] also requires the underlying fork error to surface in the UI. The change only logs a warning and the reported verification states that no UI error-visibility change was made.Resolution
Expose the underlying
forkThreadfailure to the user through the existing UI error path, while retaining the fresh-session recovery and transfer resolution behavior.
-
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.
Baseline: Scenario and decoded snapshot size10 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.
Updated in place by a trusted workflow. PR artifacts are strictly validated and never executed. |
Problem
Suppose a thread is forked natively (same provider, "Fork from this response") and its source thread's native provider session no longer exists, for example because it was deleted from OpenCode's session database. Then every send in the fork fails with "Provider turn failed to start", forever. Each
provider-turn.starteffect retriesforkThread5 times against the missing session. The fork transfer stayspending, and the orchestrator re-targets it at every new run, so the fork can never recover.To reproduce on main: start an OpenCode thread with two turns. Delete its native session (
opencode api DELETE /api/session/<id>). Fork from the first reply and send. Every send fails withSession not found: ses_….Change
ProviderTurnStartServicenow treats a native fork the way it already treats a failed resume:willRetryis true, a failedforkThreadstill rethrows, so transient failures (for example a source session that is still working) keep their retries and a later attempt can still fork natively.ensureThreadwithforceNewNativeThread). That session receives the fork's history as afull_thread_summarycontext handoff, built from the source thread's items up to the fork point, as the orchestrator's portable fork does. The fork transfer is resolved asresolved_portable/portable_context, so later sends resume the fresh session and never callforkThreadagain. The fresh provider thread'sforkedFromis cleared because it did not inherit the source's native history.A fork whose source session is healthy is unchanged: it still forks natively.
Scope and approval
Fixes #27709
Triaged via the automated issue flow.
Verification
Focused tests, in
apps/server/src/orchestration-v2/ThreadFork.execution.test.ts:a native fork whose source session is gone starts fresh with the source history. On main this fails withexpected [ 'Provider turn failed to start' ] to deeply equal []. With the fix it passes, and the source user message reaches the fresh session's turn input.a native fork that fails before its last attempt still forks nativelyasserts 2forkThreadcalls and aconsumed/native_forktransfer. It fails if thewillRetryguard is removed.vp run check:changedpasses: format, lint, knip, typecheck, and 43 tests across the 3 related files.Runtime transcript from the real app: web dev server plus the server CLI, OpenCode 2.2.4,
anthropic/claude-haiku-5-5. Each side ran on a fresh synthetic database (T3CODE_DEV_SEED=0). Steps: a parent thread with "Reply PARENT-ONE, remember the codeword BLUEBIRD", then a second turn, then delete the parent's native session, then "Fork from this response" on the first reply in the web UI, then three sends in the fork.BLUEBIRD(the parent's history reached the fresh session)status: failed,error: Session not found: ses_e722739a…status: completed, "Codeword: BLUEBIRD / First message reply: PARENT-ONE"provider-turn.startattempts per sendpending, re-targeted at run 3resolved_portable/portable_contextWARN Starting a native fork in a fresh native sessionREDFOXREDFOX, transferconsumed/native_fork, 1 attemptNot checked: forks from Codex or Claude sources whose native session is gone. The fallback is in the shared turn-start path and is provider-agnostic, but only OpenCode was run end to end. Screenshots of the two web UI states were captured, but the upload tool was unavailable on the triage machine, so this PR includes none. The table above is the transcript.
Model and harness: Claude Opus 5.5 (claude-opus-5-5) in Claude Code, running in T3 Code; reviews by GPT-6.1 Sol in Codex and Claude Opus 5.5.
🤖 Generated with Claude Code