fix(server): checkpoint restore ignores the cwd of a provider session shared across threads - #14502
Conversation
… shared across threads Codex (and OpenCode 2) open one provider session per instance for every thread. That session keeps the cwd it was opened with, usually the first thread's folder, so another thread bound to it looked like it worked in that folder and blocked its file restore. Skip shared sessions when collecting another thread's folders; its worktree or project root and checkpoint scopes already cover where its turns run. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
ApprovabilityVerdict: Approved at Macroscope's review found this PR approvable — This is a narrowly scoped checkpoint-restore bug fix: pooled provider sessions are no longer treated as evidence of workspace overlap, while exclusive sessions and other isolation checks remain unchanged. A focused regression test covers the new behavior. You can add or adjust custom eligibility rules. Learn more. |
Thread transfer impact✅ Thread transfer remains within every enforced ceiling.
Baseline: unavailable · PR result: 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. |
f48d257
into
t3code/codex-turn-mapping
Problem
#14370 made
isCheckpointRestoreIsolatedtreat the cwd of every non-stopped provider session bound to another thread as a folder that thread works in. Adapters withsessions.supportsMultipleProviderThreadsPerSession: true(Codex, and OpenCode 2) share one provider session per instance across all threads (providerSessionIdForinOrchestrator.ts). That session keeps the cwd it was opened with, usually the first thread's folder. So when threads A and B both use Codex in separate worktrees, B's bound session reports A's folder, and A's Revert files too is refused with "File restore requires an isolated worktree". This was seen live on the OpenCode 2 stack: a thread forks, the fork moves to its own worktree, and the source's rollback is refused.Fix
When collecting another thread's folders, skip sessions whose capabilities say they are shared across threads. Those sessions say nothing about where that thread works: every turn runs in the thread's
runtimePolicy.cwd, which is itsworktreePathor its project root. The check already collects both, plus its checkpoint scope cwds.Single-thread sessions (Claude, Cursor, OpenCode 1, ACP, Pi, Grok) keep #14370's rule, including the errored session that still has a live event stream. The session already carries its capabilities, so this needs no new projection columns.
Verification
Run in a fresh worktree off
t3code/codex-turn-mapping(f3eb99e83b, which includes the pid-1 guard from #14461).TMPDIRwas under/home, and tests ran insideunshare -U --map-current-user -p -f --mount-proc.CheckpointRestoreSafety.test.tsowner=shared-provider: the other thread is in a sibling worktree, bound to a running shared session whose cwd is this thread's worktree. It runs the real rollback service against a temp directory.File restore requires an isolated worktree(1 failed, 11 passed).["provider", "files"]).vp test run CheckpointRestoreSafety.test.ts CheckpointRollbackService.test.ts runtimeLayer.test.ts: 3 files, 77 tests passed. That includes all of #14370's cases:nested,ancestor,archived-nested,aliased-nested,project,scope,provider,errored-provider, thesibling/stopped-provider/conversationcontrols, and the runtime-layer admission rejection.vp exec tsc --noEmit -p .inapps/server: exit 0, noerror TS.vp lintandvp fmton the two touched files: clean. No imports were added.Not run: a live Codex or OpenCode 2 fork-then-rollback repro, or a replay fixture. Replay fixtures create threads with
worktreePath: nulland roll back withrestoreFiles: false, so they never reach this check.Model: Claude Opus 5.5 (Claude Code)
🤖 Generated with Claude Code