PR #9806 test report
CI and review checks pass. Human approval remains required.
The cleanup bug is real and this PR fixes the tested cases. After fixing the live-session reuse finding, 76 focused tests passed across 11 files on head 87d7bcf6, with one optional fixture-recording test skipped. The earlier rebased head passed 363 tests across 20 files. Server typechecking, targeted lint, and formatting passed.
Rebased the four PR commits onto V2 base 3ed8c4df and then pushed the review fix as 87d7bcf6. Current head is b0ccd56f, which adds only the CI transport repair described below. Full CI and the correctness/conventions reviews pass on this head. The conflict was in open(), where the base added a workspace-folder check and this PR added locking. Both behaviors are preserved. GitHub confirms the branch is mergeable.
Tested September 5, 2026, UTC. Linux x64, Node 24.20.0, pnpm 11.10.0. Pull request.
App screenshots
Captured in a real Chromium client connected to an isolated local server and the real Codex provider. Both versions used the same temporary fault: start idle cleanup after 5 seconds and delay scope finalization for 120 seconds. The production 30-second cleanup timeout was unchanged. These hooks were removed after capture and are not in the PR.
Before: no warning while cleanup remains pending
V2 base 3ed8c4df marks the session stopped after 30 seconds, with no error in the app, even though cleanup has not finished.

After: cleanup timeout is visible
PR b0ccd56f persists status error and shows the cleanup warning. Replacement sessions remain blocked until cleanup completes.

Recovery: warning clears after cleanup succeeds
The same PR session finishes the delayed cleanup. Its status becomes stopped and its last error clears; the banner disappears without dismissal or a page reload.

Review finding fixed
Bugbot found that a live replacement could become unusable while its predecessor cleaned up. Two new regressions reproduced the problem. The cleanup checks now allow reuse when the thread is already attached to another live session. New sessions, reuse of the releasing session ID, and new attachments of blocked threads still fail. The tests cover pending cleanup, the 30-second timeout, and eventual cleanup success or failure. Both regressions now pass.
The review thread is resolved. Source, tests, and cleanup documentation were updated in commit 87d7bcf6.
Bug reproduction
A provider scope stays open after cleanup starts. The test clock advances through the idle-release delay and the 30-second cleanup timeout. The provider's close counter remains zero.
| Check | Old cleanup | PR head |
| Persisted session status | stopped, incorrect | error, correct |
| Replacement on the same thread | Opens before cleanup finishes | Rejected while cleanup remains pending |
| Regression assertions | 2 fail as expected | 2 pass |
The comparison used the session manager from the original V2 base, f2e4a0de. I repeated it with the manager and its new workspace-error dependency from current base 3ed8c4df, using the PR's test fixtures. Both bugs still reproduce. This was a focused comparison of those implementations, not a full test run of current base.
CI download repair
The general Test and Check jobs repeatedly failed or timed out downloading Ubuntu packages over HTTP, including on fresh-runner retries. Commit b0ccd56f changes the Blacksmith Ubuntu mirror list and Ubuntu security source to HTTPS before those jobs install packages. It keeps source hosts, package signatures, package choices, and test commands unchanged. The change is isolated in its own commit.
Workflow YAML parsing, shell syntax, formatting, and a fixture check of the URL conversion passed. Server code is unchanged from 87d7bcf6.
Earlier rebase verification
Before the review fix, rebased head bc10499e passed the focused set below in 88 seconds. It includes the newer base's checks for a missing workspace, a file used as a workspace, and a workspace deleted after its session opened. The skipped test records a new Claude SDK fixture and requires explicit opt-in.
| Test file | Result |
|---|
| KeyedSerialExecutor.test.ts | 2 passed |
| Pr9806RegressionProof.test.ts | 2 passed |
| Pr9806Verification.test.ts | 4 passed |
| ProviderRuntimeRecoveryService.regression.test.ts | 1 passed |
| ProviderRuntimeRecoveryService.test.ts | 11 passed |
| ProviderSessionManager.test.ts | 33 passed |
| ProviderSessionTransitionPolicy.test.ts | 8 passed |
| ProviderSwitchService.test.ts | 2 passed |
| SelectionRestart.integration.test.ts | 2 passed |
| AcpAdapterV2.test.ts | 96 passed |
| AntigravityAdapterV2.test.ts | 5 passed |
| ClaudeAdapterV2.test.ts | 76 passed |
| CodexAdapterV2.test.ts | 55 passed |
| CursorAdapterV2.testkit.test.ts | 3 passed |
| GrokAdapterV2.test.ts | 27 passed |
| OpenCodeAdapterV2.test.ts | 21 passed |
| ClaudeReplayFixtures.integration.test.ts | 4 passed, 1 skipped |
| CodexReplayFixtures.integration.test.ts | 3 passed |
| OrchestratorReplayRecovery.integration.test.ts | 2 passed |
| ProviderSwitch.integration.test.ts | 6 passed |
The independent probes checked these behaviors:
- A thread previously detached from a shared session stays blocked while that session cleans up, then can attach again after cleanup succeeds.
- Cancelling the original close caller leaves cleanup running. A later caller waits for that same cleanup.
- Failure to persist timeout status does not admit a replacement before cleanup. A waiting retry observes the reporting failure; successful resource cleanup still permits a later open.
- A terminal detach of an exclusive session keeps its thread blocked until cleanup completes.
Each probe also checked that an unrelated thread could open during the hang, the old session ID could not be reused by another thread, and only one cleanup ran.
The existing manager tests covered close and detach timeouts, repeated calls, failed finalizers, error redaction, late completion, credential revocation and reuse, concurrent opens and closes, idle expiry, busy sessions, event-stream failure, pending runtime requests, shared-session detach, and shutdown.
Latest checks and limits
| Server typecheck | Passed. Effect emitted suggestions in existing files, with no type errors. |
| Lint | Passed for the changed manager and test file. |
| Formatting | Passed for all three changed files. |
| GitHub CI | Full CI passed on current head b0ccd56f, including Check, Test, all three server shards, release smoke, Rust, and mobile native static analysis. |
| Review checks | Cursor Bugbot, Macroscope correctness, and Effect Service Conventions pass on b0ccd56f. All review threads are resolved. Macroscope approvability is neutral and requires human approval because the production lifecycle and credential changes exceed its automatic-approval scope. |
| Mergeability | GitHub confirms MERGEABLE against base 3ed8c4df. |
| Clients and connections | No client or wire-schema changes. Server logic is shared by web, desktop, and mobile. The local web client was exercised with real Codex turns and induced slow cleanup; screenshots appear above. Native-client, relay, and tunnel flows were not exercised. |
| Providers | Focused tests covered Codex, Claude, Cursor, Grok, OpenCode, Antigravity, and the shared ACP adapter. Mock and replay fixtures were used; no live account-switching session was run. |
This PR does not change the policy for detaching from a shared session, or establish safe account handoff inside a shared Codex process. Those remain separate work, as the PR states.
Commands and test files
Working directory: apps/server
Runtime prefix: npm exec --yes --package=node@24.20.0 -- pnpm exec
vp test run
src/orchestration-v2/KeyedSerialExecutor.test.ts
src/orchestration-v2/Pr9806RegressionProof.test.ts
src/orchestration-v2/Pr9806Verification.test.ts
src/orchestration-v2/ProviderRuntimeRecoveryService.regression.test.ts
src/orchestration-v2/ProviderRuntimeRecoveryService.test.ts
src/orchestration-v2/ProviderSessionManager.test.ts
src/orchestration-v2/ProviderSessionTransitionPolicy.test.ts
src/orchestration-v2/ProviderSwitchService.test.ts
src/orchestration-v2/SelectionRestart.integration.test.ts
src/orchestration-v2/Adapters/AcpAdapterV2.test.ts
src/orchestration-v2/Adapters/AntigravityAdapterV2.test.ts
src/orchestration-v2/Adapters/ClaudeAdapterV2.test.ts
src/orchestration-v2/Adapters/CodexAdapterV2.test.ts
src/orchestration-v2/Adapters/CursorAdapterV2.testkit.test.ts
src/orchestration-v2/Adapters/GrokAdapterV2.test.ts
src/orchestration-v2/Adapters/OpenCodeAdapterV2.test.ts
src/orchestration-v2/testkit/ClaudeReplayFixtures.integration.test.ts
src/orchestration-v2/testkit/CodexReplayFixtures.integration.test.ts
src/orchestration-v2/testkit/OrchestratorReplayRecovery.integration.test.ts
src/orchestration-v2/testkit/ProviderSwitch.integration.test.ts
tsgo --noEmit
From repository root:
vp lint apps/server/src/orchestration-v2/ProviderSessionManager.ts apps/server/src/orchestration-v2/ProviderSessionManager.test.ts
vp fmt --check apps/server/src/orchestration-v2/ProviderSessionManager.ts apps/server/src/orchestration-v2/ProviderSessionManager.test.ts docs/orchestration-v2/provider-switching-and-context.md
Testing used an isolated checkout because the shared repository had a corrupt Git object. The four PR commits were rebased and pushed with an explicit lease against the prior head. The PR changes the manager, its tests, the cleanup docs, and the CI download transport. Temporary probes and JSON evidence are outside the checkout at /tmp/pr9806-evidence. The two live-session reuse regression cases are committed in the PR. The PR has not been merged.