🤖 fix: session disposal does not wait for a stopping migration - #5592
Conversation
Codex Review SummaryThis comment shows the latest Codex review activity on this pull request.
ℹ️ About Codex in GitHubYour team has set up Codex to review pull requests in this repo. Reviews are triggered when you
Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings. |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: eee83d782a
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
|
Readiness record for head 450bb83:
Generated with |
…ep 2 (coder#5595) ## Summary The three foreground-to-background migration tests in `tests/ipc/runtime/backgroundBashDirect.test.ts` relied on a fixed `sleep 2`: the command had to keep running for 2 s while the bash tool claimed a record name and migrated it. On Windows CI that migration can take longer, so the command finished first and the tool correctly took the normal completion path (coder#4878). Now each script waits for a release file that the test creates after it has checked the migration. The test then waits for the process to exit instead of sleeping 2.5 s. This is a test-only change. Fixes coder#5593 ## Not a regression from coder#5579 or coder#5592 The same test failed on Windows before coder#5579 (merged 2026-10-03): - 2026-09-26, run 36272619908 (coder#4740, merge queue), logged on coder#4463. - 2026-09-27, run 36284062610 (coder#4794), logged on coder#4463. In this one, the `AFTER_BG` line was already in the output when the migration returned, which is the same timing problem. coder#5579 and coder#5592 only change the refused and failed migration paths. The successful migration path that these tests use is unchanged. ## Root cause In the merge-queue failure (run 37154303115), `result.success` was true but `result.backgroundProcessId` was `undefined` (`backgroundBashDirect.test.ts:502`). So the tool returned a normal completion. `bash.ts` first awaits the migration's name claim, then checks whether the command already exited, and if so completes normally. With a 2 s command, any claim slower than about 2 s produces exactly this result. Jest reported the failure location as `afterAll`, line 439, but the failing assertion is the one at line 502. ## Validation - Reproduced on Linux with a probe (not committed) that delays `claimMigrationProcessId` by 2.5 s, which matches a slow Windows claim. On main, all three tests fail with the CI symptom: ```text ● Foreground to Background Migration › should migrate foreground bash to background and continue running Expected: "fg_to_bg_…" Received: undefined ● … › should preserve output across stream boundaries (Received: undefined) ● … › should not kill backgrounded process when abort signal fires (Received: undefined) ``` With this change and the same probe, all four tests in that describe pass. - Without the probe, the whole file passes (11 tests, `bun x jest`, Bun 1.3.12). `make static-check` passes. - Windows: the gate path uses forward slashes (`C:/…`), which Git Bash accepts. The scripts never escape backslashes. --- _Generated with `xum` • Model: `anthropic:claude-opus-5-5` • Thinking: `high` • Cost: `$47.22`_ <!-- mux-attribution: model=anthropic:claude-opus-5-5 thinking=high costs=47.22 -->
Summary
Since #5579, a refused or failed background migration whose exit is never confirmed stays pending for the rest of the backend process. Session disposal's
cleanup(workspaceId)drains pending entries with no deadline, so it hung forever and kept its admission seal. After that, every later move to the background in that workspace was refused, andsession.endand listener removal never ran. Now session disposal does not wait for a migration whose command is being stopped. It finishes and releases its seal. Removal and archive still wait for that migration and fail closed at their 60 s drain deadline.Fixes #5589
Implementation
beginMigration()returns amarkStopping(). The bash tool calls it where it hands a refused or failed migration to the command's exit (🤖 fix: keep a refused or failed migration pending until its command exits #5579).WeakSetbeside the existing pending set. No new persisted state.drainPendingAdmissionswithout a deadline (only session disposal uses that form) skips stopping entries. With a deadline (removal and archive,failClosedAfterDrainTimeout), it waits for every entry as before.markStopping()therefore also wakes a disposal drain that is already waiting, and the drain re-checks its entries. Test: "lets session disposal finish once a migration it waits for becomes stopping" (backgroundProcessManager.test.ts). Without the wake it times out withExpected: "finished", Received: "waiting".Why this rather than a disposal deadline (option a) or a process-group probe (option b)
cleanupdoc forbids that: the spawn could register after disposal lifted its seal, with no cleanup left to stop it. The deadline would also add a timer and an arbitrary wait to every disposal.Validation
Test-first: "a migration whose exit is never confirmed blocks removal but not session disposal" in
backgroundProcessesFormalRepro.test.ts. Its fake runtime rejectsexitCodeafter the kill, so the exit is never confirmed. Without the fix, disposal is still waiting after 2 s:With the fix, disposal finishes, a later
beginMigrationis admitted (the seal is released), and a removal'scleanup(ws, { failClosedAfterDrainTimeout: true })is still waiting. The test helper'safterEachdisposal cleanup no longer needs a special case for this runtime.Bun 1.3.12: the repro file (16 tests),
bash*,backgroundProcessManager,workspaceService.archive/remove,serviceContainerandagentSession.disposeRacepass (459 tests).make static-checkpasses. One run hit an unrelated flake in a non-host spawn test. It passed alone 3/3 here and 2/2 on main, and it is filed as 🤖 flake: two backends' same-name non-host spawns got one record directory under load #5591.Formal:
formal/background-processesmodels removal and archive only, not session disposal, and this PR changes no formal files. Removal and archive behaviour is unchanged.Risks
Low. The change only affects session disposal while a stopping migration is pending. Disposal now returns while that command may still be stopping. Disposal never deleted anything, and the entry stays tracked for removal and archive.
Generated with
xum• Model:anthropic:claude-opus-5-5• Thinking:high• Cost:$44.17