Skip to content

fix(server): forks whose source session is gone start fresh instead of failing every send - #27729

Open
juliusmarminge wants to merge 5 commits into
mainfrom
fix/issue-27709
Open

juliusmarminge wants to merge 5 commits into
mainfrom
fix/issue-27709

Conversation

@juliusmarminge

Copy link
Copy Markdown
Member

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.start effect retries forkThread 5 times against the missing session. The fork transfer stays pending, 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 with Session not found: ses_….

Change

ProviderTurnStartService now treats a native fork the way it already treats a failed resume:

  • While willRetry is true, a failed forkThread still rethrows, so transient failures (for example a source session that is still working) keep their retries and a later attempt can still fork natively.
  • On the final attempt, the turn starts in a fresh native session (ensureThread with forceNewNativeThread). That session receives the fork's history as a full_thread_summary context 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 as resolved_portable / portable_context, so later sends resume the fresh session and never call forkThread again. The fresh provider thread's forkedFrom is 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 with expected [ '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 natively asserts 2 forkThread calls and a consumed / native_fork transfer. It fails if the willRetry guard is removed.
  • vp run check:changed passes: 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.

main 8d012a6 (before) this branch 9740125 (after; the implementation is the same at head)
Send 1 in the fork (web UI) "Provider turn failed to start · Session not found: ses_e722739a…" Replies BLUEBIRD (the parent's history reached the fresh session)
Sends 2 and 3 status: failed, error: Session not found: ses_e722739a… status: completed, "Codeword: BLUEBIRD / First message reply: PARENT-ONE"
provider-turn.start attempts per send 5, 5, 5 5 (then the fallback), 1, 1
Fork transfer pending, re-targeted at run 3 resolved_portable / portable_context
Server log — WARN Starting a native fork in a fresh native session
Fork from the latest reply (variant) fails the same way —
Control: fork of a healthy parent completes, REDFOX completes, REDFOX, transfer consumed / native_fork, 1 attempt

Not 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

juliusmarminge and others added 5 commits October 30, 2026 17:28
…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>
@github-actions github-actions Bot added vouch:trusted PR author is trusted by repo permissions or the VOUCHED list. size:M 30-99 changed lines (additions + deletions). labels Oct 30, 2026
coveredRunOrdinals: { from: 1, to: sourceRun.ordinal },
strategy: "full_thread_summary",
runs: sourceProjection.runs,
items: (yield* projectionStore.getTurnStartHistory(sourceProjection.thread.id)).filter(

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟠 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.

@macroscopeapp

macroscopeapp Bot commented Oct 30, 2026

Copy link
Copy Markdown
Contributor

Approvability

Verdict: 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:

  • 1 blocking correctness issue found at or above your repo's Minimum Blocking Severity

Adjust the Minimum Blocking Severity for this repo — including turning it Off — in Settings. You can add or adjust custom eligibility rules. Learn more.

@coderabbitai

coderabbitai Bot commented Oct 30, 2026

Copy link
Copy Markdown
Contributor

Review in Change Stack →

No actionable comments were generated in the recent review.

Recent review info
Run configuration
Commits

Reviewing files that changed from the base of the PR and between 6fc9144 and 02e6178.

Files selected for processing: 2
  • apps/server/src/orchestration-v2/ProviderTurnStartService.ts
  • apps/server/src/orchestration-v2/ThreadFork.execution.test.ts

Included review availability: This review used your included allowance. Your plan provides up to 10 included reviews per hour; 5 remain after this review.

Walkthrough

When a native fork fails during an active attempt, provider turn startup creates a fresh native thread and prepares a portable handoff containing eligible source history. The provider thread clears forkedFrom, and tests cover persistent fork failures and later native-fork success.

Changes: Native fork recovery

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: Normal

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
Loading

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: Passed 3 · Failed 1

Failed Failed checks: 1 warning

Check name Status Explanation Resolution
Linked Issues check Warning The implementation satisfies the recovery requirement in [#27709]. It retries forkThread while willRetry is true. On the final attempt, it creates a fresh native session, prepares a `full_thread_s… Expose the underlying forkThread failure to the user through the existing UI error path, while retaining the fresh-session recovery and transfer resolution behavior.
Passed Passed checks: 3 passed
Check name Status Explanation
Title check Passed The title clearly and concisely describes the main change: recovering native forks by starting a fresh session when the source session is gone.
Description check Passed The description includes complete Problem, Change, Scope and approval, and Verification sections. It provides reproduction steps, implementation details, linked issue context, focused test results, ru…
Out of Scope Changes check Passed The changed service logic directly implements [#27709] recovery for a missing native fork session. The added tests verify source-history handoff, transfer resolution, cleared forkedFrom, and preserv…
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, cleared forkedFrom, 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 retries forkThread while willRetry is true. On the final attempt, it creates a fresh native session, prepares a full_thread_summary handoff from the source history, resolves the transfer as resolved_portable / portable_context, and clears forkedFrom. 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 forkThread failure 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.

@github-actions

Copy link
Copy Markdown
Contributor

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.2 KiB 1.3 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.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.3 KiB 1.3 KiB 0 B (0.0%) 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: 6fc9144 · PR result: 02e6178 · 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.

@juliusmarminge juliusmarminge added bugfix Fix PR opened by an issue lead; enters the PR loop pr:gate-failed A land gate failed labels Oct 30, 2026

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

bugfix Fix PR opened by an issue lead; enters the PR loop pr:gate-failed A land gate failed size:M 30-99 changed lines (additions + deletions). vouch:trusted PR author is trusted by repo permissions or the VOUCHED list.

Projects

None yet

1 participant