Repository navigation
Retry Codex lock contention instead of refusing restores on relaunch - #20499
Conversation
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
During a relaunch dozens of Codex threads resume at once, each briefly holding thread-writer-locks/.coordination.lock. The CLI's final exec guard treated that `.changing` probe like an unreadable lock and refused an unowned conversation. It now re-probes `.changing` for a bounded ~3.5s, woken early by lock-namespace file events, while `.active` and `.unavailable` still refuse at once with case-specific messages. The app admission path already kept `.changing` recoverable, but waited up to 8s per round for a file event a flock release never raises; it now re-probes after 250ms. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Bugbot needs on-demand usage enabledBugbot uses usage-based billing for this team and requires on-demand usage to be enabled. A team admin can enable on-demand usage in the Cursor dashboard. |
|
All contributors have signed the CLA ✍️ ✅ |
|
Warning Review limit reachedYou've used all free OSS reviews for now. Wait for the free limit to reset to keep reviewing this public repository. Next included review available in 3 minutes. View limit details
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
Review found that a 250ms evidence bound turns a coordination lock that never settles into ~4 admission RPCs per second per pane. Keep the 8s request bound and instead re-probe `.changing` on the settle bounds within the same request, with an app-host regression for the waiting request. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Merge-main commit by scripts/merge-main.sh. Merged by scripts/merge-main.sh: origin/main at 28e8640, the newest commit with green CI fast guards (1 newer skipped). Resolved conflicts: - Resources/Localizable.xcstrings: xcstrings key-level union Merge-main-previous-head: 1c8fc56 Merge-main-base: 28e8640
|
Correctness-first review (subagent), findings and resolution
Checked and found fine: Swift 6 compile concerns (default-argument function reference, method references, Sendable captures), no fd leaks, and |
|
Merge receipt for
Labeled |
|
Probably yours: CI failed on
Failures:
Matched log linesNot re-run automatically: Written by |
|
main -> feat-cmux-next sync (2026-10-14, bead cx-q1lnl): this PR added 1 file(s) under the legacy macOS app / legacy Swift packages, which feat-cmux-next (the cmux-next rewrite) has deleted. The sync removes them on feat-cmux-next only (main is unchanged):
No action is needed on main. |
Summary
On relaunch,
cmux restore codex <id>could refuse an unowned conversation with "cmux could not verify whether this agent session is already running." When dozens of Codex tabs resume in the same second, each new Codex briefly holdsthread-writer-locks/.coordination.lock. The CLI's final exec guard probed once, saw.changing, and failed it the same way as an unreadable lock.The final guard now re-probes
.changingfor a bounded ~3.5 s (backoff 20 ms → 1 s). It wakes early when Codex creates, renames or unlinks a thread lock, and otherwise re-probes at each bound; a flock release raises no file event. The shared policy isCodexWriterLockSettlein CMUXAgentLaunch..activestill refuses at once with the existing held-writer notice, and.unavailablestill refuses at once. Each case now has its own stage and message:session.writer-lock-held,session.writer-lock-changing("Codex was still updating its session locks…"), andsession.writer-check-unavailable("cmux could not read this conversation's Codex session lock…").App-side admission (
agent.restore.admit) already treated.changingas recoverable, so the CLI kept retrying. Each waiting request spent up to 8 s on a file event, though, and a coordination release never raises one, so a burst could stall each restore for 8 s per round. A waiting request on.changingnow re-probes inside the same request on the same settle bounds, still woken early by lock-directory events. The request stays bounded at about 3.5 s, and a lock that never settles adds no extra RPC rounds. Every other state keeps the 8 s event wait.The re-probe bounds are timed waits, not synchronization: releasing a flock raises no event to wait on, so a bound is the only signal available. Each is capped and also ends early on a lock-namespace file event.
Testing
7e1dd03/ green59558c1:CLICodexWriterLockRestoreTests/coordinationContentionIsTransientruns the bundled CLI with another holder on the coordination lock.releases: truereleases it 0.4 s after the CLI's socket request and expects the restore to exec.releases: falsekeeps it held and expects the "still updating" failure, not the generic one. Red receipts are pending: an app-host rerun of the test-only commit needs a main commit with CI-built products, and the nearest one is still building. On a slow host,releases: truecould reach the guard after the release and pass without retrying;releases: falsealways exercises the bounded retry.CodexWriterAdmissionRecoveryTests/coordinationContentionResolvesWithinOneWaitingRequestholds coordination, releases it after 0.3 s, and expects the waiting admission to return in under 4 s (previously 8 s) and the next one to admit. Added after the fix, so it has no separate red run.CodexWriterLockTestscovers the settle policy: it retries only.changing, stops when contention ends, spends exactly its budget when contention never ends, never retries.activeor.unavailable, and wakes on a lock-file change. It runs in the package test lane. The HQ build host fornx-remotewas unreachable from this machine, so these ran only in CI.python3 scripts/verify-local.py: 7/7 selected checks passed (syntax, xcstrings, localization parity, defaultValue parity, test wiring, package groups, feature flags).scripts/localize-changes. The now-unusedagentRestore.admission.unavailablekey was removed.1c8fc56.Changelog
Fixed: Relaunching cmux with many Codex tabs no longer leaves some of them unrestored with "could not verify whether this agent session is already running"
Proof
CLI/runtime behavior; evidence is the CLI regression above plus the tagged-build relaunch check.
🤖 Generated with Claude Code