Repository navigation
Conversation
Startup recovery reconciles threads one at a time and marks each bound provider session as stopped. Codex and OpenCode 2 threads share one session per provider instance, so after the first thread on it was reconciled, every later thread read the session as stopped and was refused a restart continuation. Continuation eligibility is now judged against each session as the recovery pass first read it, before any thread in the pass stopped it. A session that was already stopped or in error before the pass still blocks continuation.
ApprovabilityVerdict: Approved at Macroscope's review found this PR approvable — This is a narrowly scoped restart-recovery bug fix that preserves initial shared-session state so all eligible Codex threads can receive continuation effects. It adds targeted coverage for shared live sessions and preserves the existing behavior for sessions already stopped before recovery. You can add or adjust custom eligibility rules. Learn more. |
|
Important Review skippedWe couldn't safely recover the incremental review. No full review was started, and the last reviewed checkpoint was preserved. Retry later, or explicitly request a full review by commenting You can disable this status message by setting the Use the checkbox below for a quick retry:
No actionable comments were generated in the recent review. 🎉 ℹ️ Recent review info
📝 Walkthrough
Merge Risk | ⚪ Minimal · up to
|
| Check name | Status | Explanation |
|---|---|---|
| Title check | ✅ Passed | The title clearly identifies the main change: restarting continuations for every eligible Codex thread. |
| Description check | ✅ Passed | The description clearly covers the problem, change, scope, verification, and limitations. The scope section refers to the issue’s triage comment but does not include the explicit maintainer approval c… |
| Linked Issues check | ✅ Passed | Issue #15749 requires startup recovery to continue each eligible running thread after shutdown preparation fails. ProviderRuntimeRecoveryService.reconcile now records each session's state before its… |
| Out of Scope Changes check | ✅ Passed | The changes are limited to startup recovery session-state handling and its recovery tests. The test fixture changes support the shared-session scenarios required by issue #15749. The PR does not chang… |
| Docstring Coverage | ✅ Passed | No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check. Docstring coverage is scoped to functions touched by this diff. Analyzed 0 functions across 2… |
✨ Finishing Touches
-
🧪 Generate unit tests (beta)
-
- Create a new PR
-
- Autopilot · Keep fixing CodeRabbit findings and required CI, and resolving merge conflicts
Comment @coderabbitai help to get the list of available commands.
|
Independent verification of #15749 on current
The test port only needed the current-main rename from vp test run apps/server/src/orchestration-v2/RestartContinuation.test.ts apps/server/src/orchestration-v2/ProviderRuntimeRecoveryService.test.ts apps/server/src/orchestration-v2/ProviderRuntimeRecoveryService.regression.test.ts apps/server/src/orchestration-v2/ProviderRuntimeRecoveryPerformance.test.tsI also tested pre-reading entire recovery projections before reconciliation. Although that restores continuation, the same SQLite fixture emits four session-stop events instead of one. This PR's first-seen session cache avoids that duplication, retains only shared session rows, and keeps other per-thread projection data fresh. This confirms the existing fix covers the independently reproduced case; no competing PR is needed. |
Fixes #15749
Problem
If shutdown preparation does not finish before the launcher force-kills the server, startup recovery records the restart continuations. It reconciles threads one at a time and, for each one, marks every live session it is bound to as
stopped. Codex and OpenCode 2 threads all share one session per provider instance (provider-session:provider-instance:<id>:shared). The first thread reconciled on that session, often an older idle thread, stopped it. Every later thread then read the session asstopped, andrestartContinuationRunrefused a live turn on a stopped session. So at most one thread per shared session was continued, and none if an idle thread came first. Claude was not affected because each Claude thread has its own session.Change
During one recovery pass,
ProviderRuntimeRecoveryService.reconcileremembers each session as the pass first read it. Continuation eligibility is judged against that state, which means "was the session live when the server went down", rather than against the state a previous thread wrote during the same pass. Session stops, per-thread fresh reads andRestartContinuation.tsare unchanged. A session that was alreadystoppedorerrorbefore the pass still blocks continuation.The triage comment on the issue names two directions. This is the first one: decide eligibility before any thread stops a shared session. The second, not stopping a shared session while other bound threads still have running runs, would change the session lifecycle; the outcome for the user is the same either way.
Verification
New tests in
ProviderRuntimeRecoveryPerformance.test.tsuse the real SQLite stores. They seed one older idle thread and three running Codex-style threads, all on one shared session, then run startup recovery:efecd3cf8bstartup recovery continues every running thread on a shared provider sessionstartup recovery does not continue threads whose shared session was already stoppedrestart recovery records one continuation per active thread and is idempotent(existing, one session per thread)vp test runon the recovery, restart-continuation, projection-recovery, runtime-layer and replay-recovery suites (12 files), before the rebase(cd apps/server && vp test run src/orchestration-v2/ProviderRuntimeRecoveryPerformance.test.ts src/orchestration-v2/ProviderRuntimeRecoveryService.test.ts src/orchestration-v2/RestartContinuation.test.ts)ond4cd794bfe(rebased ontofe417dff4c)T3_BENCH_RECOVERY=1benchmark matrix, run during independent review(cd apps/server && vp run typecheck)error TSorwarning TSvp lint/vp fmt --checkon the two changed filesknip --workspace apps/server --exportsServer-only change; there is no UI, so no screenshots.
Limitations / not checked: no real service restart and no real Codex or OpenCode run, so native resumption of several threads on one shared session is taken from the code path (
ProviderTurnStartServicereopens the stopped session per native thread), not observed. A shared session with one running thread and one sibling whose provider turn was not running is not covered by a test; the unchanged eligibility check still declines that sibling. A second crash midway through recovery is a pre-existing limit of this pass-local view. The launcher's fixed 5 s grace (TERMINATE_GRACE_MS), which is why the startup path runs at all, is unchanged and out of scope.This touches the same recovery loop as #15786 (a different bug); whichever lands second needs a small rebase.
Implemented with Claude Opus 5.5, verified with GPT-6 Astra, coordinated by Claude Fable 5.1 in Claude Code.