Repository navigation
cmux NEXT: put the control socket back when its file is deleted under a running app (hq#1870) - #20471
Conversation
…gate.sh runs split, newterm, split with a slow host, new-tab and cold new-tab proofs on the GUI host and fails on a lost key; typeahead-compare.py counts keys sent by terminal id; creation-hold-proof.py is superseded (named in the gate header)
… runs (red) Two readiness soaks on one capture mini lost /tmp/cmux-debug-<tag>.sock at about 11:00Z (an external /tmp sweep); the app kept listening on the unlinked inode and no client could connect until a relaunch. These tests delete the file under a running server and expect a ping within 5 s, with the same mode, and expect a path another server took meanwhile to stay that server's. hq#1870, no bead Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…der a running app ControlSocketPathWatch watches the socket's directory with kqueue (no timer) and, once a burst of changes settles, asks the server for a new listener when nothing is at the path. The new socket is bound like the first (private staging dir, its mode, then RENAME_EXCL), so a path another server or a file took meanwhile is never replaced, and stop still unlinks only the server's own inode. Open connections keep working. The socket-file helpers move to ControlSocketServer+File.swift (move-only) to keep the server under the file budget. hq#1870, no bead Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
Important Review skippedAuto reviews are disabled on base/target branches other than the default branch. Please check the settings in the CodeRabbit UI or the ⚙️ Run configuration
You can disable this status message by setting the Use the checkbox below for a quick retry:
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 |
|
Bead |
|
Not verified: macOS jobs did not run: compile and app tests were skipped on CI passes on Written by |
The restore cancelled a listener it never resumed when the server stopped mid-bind. The other-server test now swaps the path in one rename. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Bugbot is paused — on-demand spend limit reachedBugbot uses usage-based billing for this team and has hit its on-demand spend limit. A team admin can raise the spend limit in the Cursor dashboard, or wait for the next billing cycle to continue. |
The path check runs inline on the accept queue instead of a timer task, so a restore never races an accept pausing on descriptor exhaustion. The new listener is resumed before it is published, and resumed then cancelled when the server stopped mid-restore. The watch stops after three failed restores in a row instead of re-triggering itself. Adds the wake source to the idle-wakeups plan. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Bugbot is paused — on-demand spend limit reachedBugbot uses usage-based billing for this team and has hit its on-demand spend limit. A team admin can raise the spend limit in the Cursor dashboard, or wait for the next billing cycle to continue. |
The probe moved to its own file, where the crash-safety check looks for it. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A second ControlSocketServer renamed into place restored its own path and dropped the listener the test connected to. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This comment has been minimized.
This comment has been minimized.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Three failed restores in a row stopped the watch for good, so a delete during descriptor exhaustion lost the socket until a relaunch. The watch now ignores events and retries with a capped backoff until a restore works. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- regenerated schemas/defaults/cmux-next.json Autofix-Run: https://github.com/manaflow-ai/cmux/actions/runs/38530936996
|
cmux-next tour of The tour job failed before the tour ran (build or setup); the run log has the output. The tour produced no media. Tours are data in |
… switch back is saved; every chat gets the cmux MCP server (red) (cx-wx93i) Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
… can switch back and the pick is saved; every agent chat gets the cmux MCP server (cx-wx93i)
Lawrence 2026-10-14: "give every agent chat the cmux MCP tools by default,
but by default all chats should be in skip permission mode, that user can
switch back".
- hub/start_mode.rs: once the harness is up, acpmux sets the start mode with
session/set_mode (soft, like the start model): the person's saved pick
(config.json defaults.<profile>.mode), else acpmux config.json
defaultPermissionMode, else the cmux setting agents.defaultPermissionMode
(fullAccess | standard | ask), else full access: Claude Code
bypassPermissions, the Codex bridge agent-full-access (approval never +
danger-full-access), or a harness's yolo/full-access mode. Not for Web
(remote-origin) chats, presets, folder profiles, adopted chats, or a
profile that pins its mode in argv.
- A mode pick from the person's app pane (session/set_mode or the mode
config option) is saved; Plan is a toggle and is not saved; a pick equal
to what the setting gives removes the saved mode. Only the person may
clear a saved mode (_acpmux/defaults clear), as for a stored policy.
- claude_stdio: offers bypassPermissions ("Full access") and launches Claude
Code with --allow-dangerously-skip-permissions when it accepts it (not
root, or IS_SANDBOX=1); remote chains strip the flag.
- agent_tools: the cmux MCP server (cmux mcp serve) is on for every agent
chat unless the settings file says mcp.enabled false; the settings file is
now the app's cmux-next.json (user_settings.rs), not classic cmux.json.
- cmux mcp serve: on when CMUX_AGENT_SESSION is set and mcp.enabled is not
set; any other client still needs mcp.enabled true.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
…on the shared popup row (cx-x9o52, REUSE.md slice 3) PageInfoPanel is a NativePopupPanel: placement through PopupPlacement, the host's grow/shrink motion and dismiss rules (click outside with the omnibar button kept open, Escape, key loss, app inactive, window move/resize/close). The hand placement, PopupFade and the mouse-down reopen guard go. The card no longer draws its own shadow band (PopupHostView draws the one popup shadow). PageInfoRowView is a PopupRowView. NativePopupPanel joins the child-window allow list (the bookmark bubble tripped it in DEBUG). PageInfoMotionTests (a unit test of PopupFade on PageInfoPanel) is deleted under the no-unit-test rule; the live check covers the bubble. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
…ives after the star (bookmark bubble) The star saves the bookmark at once, often before the page has a title, so the bubble showed an empty name. While the bubble shows, the page title (observed) fills the name until the user edits it; close saves it as before. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
…o on the shared host (captures) and a real click on the star (--real-click) (cx-x9o52) Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
…links open on its right side (cx-ebm.59.5, cx-ebm.59.6)
S5 (cx-ebm.59.5): Home is the daemon home workspace. On every connect the app
ensures ONE `home-channels` page tab in it (keyed new-conversation-tab on the
workspace) and moves it into a left dock column with the agent chat role
through TabMoves.toNewDockColumn (the Cmd-I dock, cx-ihyx2; width
TabMoves.leftDockWidth), never a second dock. A home from before migrates on
its first connect: its Chief conversation tabs close (the page shows the Chief
conversation) and the page moves into the dock; when nothing else stays on the
right, the move respawns a terminal there in the same daemon commit (the
daemon undocks a screen with no scrolling column). home.show reveals the home
workspace in the active window (or brings forward the window that has it). The
classic top page stays behind HomeDock.classic (false here; the setting lands
separately). New code is in CmuxNextHomeEngine/HomeDock.swift; CmuxNextApp net
lines unchanged.
S6 (cx-ebm.59.6): the home-channels page tab's onOpenExternal (a person's
click on an http(s) link) calls workspace.open_url {machine, session: current,
url} on the app's own daemon connection (WorkspaceOpenURLClient). No routing
in Swift: the daemon picks the pane and the tab.
…x with a PIN key attached; fork API 23) (cx-d0d.97)
…9df6b98) (cx-d0d.97)
… as sent (the socket deadline is now 6 s) (cx-d0d.97)
…20534) * test: a local merge commit reuses the fetched same-tree acpmux (red) Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * fix: a local merge commit reuses the fetched same-tree acpmux reload.sh on a developer Mac stopped with "no cached acpmux" for any commit no workflow published (a local integration of feat-cmux-next and PRs), even though the tree fetch had just put that tree's checksum-verified acpmux in place. build-acpmux.sh now seeds its commit cache from it when it has every requested architecture. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * chore(next): PR autofix at 7be0d24 - regenerated schemas/defaults/cmux-next.json Autofix-Run: https://github.com/manaflow-ai/cmux/actions/runs/38542356066 --------- Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com> Co-authored-by: cmux-autofix[bot] <cmux-autofix[bot]@users.noreply.github.com>
* webviews: test that every popup draws one hairline edge Red: --acpmux-popup-elevation stacks a composer-edge hairline on the one already in --ui-popup-shadow. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * webviews: popups draw one hairline, not two --acpmux-popup-elevation stacked a 0.5px composer-edge hairline on the hairline already in --ui-popup-shadow. It now is the shared popup shadow, so every menu and popover has one edge. Increase Contrast colors that one edge in the popup's ink, which the composer-edge hairline did before. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * chore(next): PR autofix at 1cc6616 - vp fmt webviews/src/agent-session/acpmux/modelPickerLayout.ts Autofix-Run: https://github.com/manaflow-ai/cmux/actions/runs/38520503432 * webviews: test that popups draw no border over the hairline and clear it under borders none Red: the pages' .ui-popup and the chat find bar draw a border over the --ui-popup-shadow hairline, and appearance.borders none leaves the hairline drawn. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * webviews: one rim on page popups and the find bar; borders none clears it The pages' .ui-popup drew a 1px border over the --ui-popup-shadow hairline; it now draws no border. The chat find bar keeps its border (its focus cue) and casts --ui-popup-drop, the shadow without the hairline. appearance.borders none sets --ui-popup-edge transparent. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * webviews: format modelPickerLayout.ts (base fmt red) Keeps cmux-autofix's vp fmt of the base file, which the base merge replaced with the unformatted copy; react-apps-check fails on it. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * webviews: the Channels palette draws one rim; test follows the summary card Red after the base merge: the new Channels palette (.lg-palette, ledger.css) drew a 1px border over the --ui-popup-shadow hairline, and the summary popover became .acpmux-summary-card. The palette now draws no border. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * chore(next): PR autofix at 54dd59d - regenerated schemas/defaults/cmux-next.json Autofix-Run: https://github.com/manaflow-ai/cmux/actions/runs/38543983469 --------- Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com> Co-authored-by: cmux-autofix[bot] <cmux-autofix[bot]@users.noreply.github.com>
…(clears the concurrency red) (#20485) * fix: clear two feat-cmux-next check reds tree-publish-v2's stub uploader takes --conflict-exit-code, which the tree publisher now passes (af8b68e). PaneAnimationsSetting carries the namespace-type justification its ratcheted sibling settings have. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * fix: keep PaneAnimationsSetting internal to CmuxNextSettings Nothing outside the module uses it, so it needs no namespace-type allow. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * chore(next): PR autofix at c2bdb98 - regenerated schemas/defaults/cmux-next.json Autofix-Run: https://github.com/manaflow-ai/cmux/actions/runs/38530638157 * fix: name the owner of ControlWorkspaceInfoCache's publish hop check-concurrency flags the unowned Task d0a62be added; it is a one-shot main-actor hop that holds the cache weakly. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * chore(next): PR autofix at 7cf07e3 - regenerated schemas/defaults/cmux-next.json Autofix-Run: https://github.com/manaflow-ai/cmux/actions/runs/38544346314 --------- Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com> Co-authored-by: cmux-autofix[bot] <cmux-autofix[bot]@users.noreply.github.com>
…hover card (#20483) * test: a notified compact row stays one line; its text reads in the hover card Leo 2026-10-14: "why is it even 2 lines in the first place?" Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * fix: compact sidebar rows are one line by default; the notification text moves to the hover card sidebar.workspaceRow.notification is now off by default (Leo 2026-10-14). #19862 turned it on for classic parity; the setting stays as an opt-in. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * chore: regenerate the settings schema hash for the notification default Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * chore: regenerate the settings schema hash on the merged base Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * chore: take base's settings exports after the base merge (regenerated next) Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * chore: regenerate the settings exports on the merged base Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * chore: refresh the defaults snapshot on the merged base Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * docs: the hover card template lists the notification fact; comments say where the text shows Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
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. |
|
Review notes (non-blocking), carried into the follow-up socket-move PR #20508:
🤖 Generated with Claude Code |
The init closed the socket and then threw with every stored property set, so deinit closed the same number again. Under parallel suites that number already belonged to another test's listener: listen failed with EBADF in passwordModeRequiresLogin, and a restored socket stopped answering in aDeletedSocketFileComesBackWithItsMode (macOS 27 run at 140aa1f). The restore tests' connect-until-up loops made it frequent. Set the stored descriptor only after the connect works. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A failed open of the socket's directory left the watch silently off, so a later deletion would go unnoticed. Log it once (review note on #20471). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…p link (#20508) * test(control): a deleted control socket file comes back while the app runs (red) Two readiness soaks on one capture mini lost /tmp/cmux-debug-<tag>.sock at about 11:00Z (an external /tmp sweep); the app kept listening on the unlinked inode and no client could connect until a relaunch. These tests delete the file under a running server and expect a ping within 5 s, with the same mode, and expect a path another server took meanwhile to stay that server's. hq#1870, no bead Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * fix(control): put the control socket back when its file is deleted under a running app ControlSocketPathWatch watches the socket's directory with kqueue (no timer) and, once a burst of changes settles, asks the server for a new listener when nothing is at the path. The new socket is bound like the first (private staging dir, its mode, then RENAME_EXCL), so a path another server or a file took meanwhile is never replaced, and stop still unlinks only the server's own inode. Open connections keep working. The socket-file helpers move to ControlSocketServer+File.swift (move-only) to keep the server under the file budget. hq#1870, no bead Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * test: a stop during a socket restore closes the new socket The restore cancelled a listener it never resumed when the server stopped mid-bind. The other-server test now swaps the path in one rename. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * fix: restore the control socket on its accept queue The path check runs inline on the accept queue instead of a timer task, so a restore never races an accept pausing on descriptor exhaustion. The new listener is resumed before it is published, and resumed then cancelled when the server stopped mid-restore. The watch stops after three failed restores in a row instead of re-triggering itself. Adds the wake source to the idle-wakeups plan. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * fix: set SO_NOSIGPIPE on the stale-socket probe The probe moved to its own file, where the crash-safety check looks for it. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * test: take the restore path with a plain listener A second ControlSocketServer renamed into place restored its own path and dropped the listener the test connected to. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * test: failing socket restores back off and still come back Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * fix: back off failing socket restores instead of giving up Three failed restores in a row stopped the watch for good, so a delete during descriptor exhaustion lost the socket until a relaunch. The watch now ignores events and retries with a capped backoff until a restore works. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * test(control): a tagged control socket lives in the user's state folder with a /tmp link (red) A host's /tmp cleaner deleted a running app's /tmp/cmux-debug-<tag>.sock two nights running (readiness soak, hq#1870). These start ControlService for a tagged bundle and expect the socket under ~/.local/state/cmux (0700), its old /tmp name a symlink that still connects and comes back when deleted, and both removed on stop. hq#1870, no bead Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * fix(control): keep cmux NEXT's control socket in ~/.local/state/cmux, with a /tmp link A host's /tmp cleaner deleted a running app's /tmp/cmux-debug-<tag>.sock two nights running (readiness soak, hq#1870). Every cmux NEXT control socket now lives in the user's state folder, ~/.local/state/cmux (created 0700), next to the release socket. Its old /tmp name stays a symlink to it: made only where nothing is, put back when deleted (ControlSocketPathWatch), removed on stop only while it is this app's link. A path too long for sun_path keeps the old /tmp name. Peer discovery scans both folders and skips the links. The bundled CLI (cmux-tui app_identity) resolves the same table and takes the old /tmp name when only it exists (an app from before the move). cmux-debug-cli.sh and readiness-soak.py prefer the new path and fall back to /tmp; docs follow. hq#1870, no bead Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * cmux-next: log when the control socket path watch cannot start A failed open of the socket's directory left the watch silently off, so a later deletion would go unnoticed. Log it once (review note on #20471). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * chore(next): PR autofix at 6352f41 - cargo fmt in Native/OptChat/optchat-chief Autofix-Run: https://github.com/manaflow-ai/cmux/actions/runs/38576384170 * test(cmux-next): a stale socket at the old /tmp name gives way to the link Red: after a pre-move app crashed, its socket file stays at /tmp/cmux-debug-<tag>.sock with nothing listening. makeLink sees something there and never makes the link, so every tool on the old path is refused. A live listener at the old name must stay. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * cmux-next: a stale socket at the old /tmp name gives way to the compatibility link A pre-move app that crashed left its socket file at /tmp/cmux-debug-<tag>.sock. makeLink saw something there and never linked, so tools on the old path were refused until someone deleted the file. Now a socket of this user that refuses a connect is replaced by the link (the rule reclaimStalePath uses); a live socket, another user's file or anything else stays, and the app logs that the link was not made. The idle-wakeups row says the /tmp watch exists only for the link. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com> Co-authored-by: cmux-autofix[bot] <cmux-autofix[bot]@users.noreply.github.com>
Summary
If something deletes the control socket file while cmux NEXT is running, the app now binds a new socket at the same path. Until now, no client could connect again until a relaunch.
The readiness soak (hq#1870) hit this two nights running. On the soak host, a process outside the app removed
/tmp/cmux-debug-<tag>.sockat about 11:00Z. The app kept listening on the unlinked inode, so every later connect got ENOENT. Agent turns kept completing because they go through acpmux, but the CLI and automation lost the app for the rest of the session.How it works:
ControlSocketPathWatchwatches the socket's directory with kqueue, with no timer. On each directory change it runs onelstaton the server's accept queue. It asks the server for a new listener only when nothing is at the path. Because the restore runs on the accept queue, it never races an accept that is pausing on descriptor exhaustion.renamex_np(..., RENAME_EXCL). A path that another server or a file took in the meantime is never replaced.stopstill unlinks only this server's own inode, and connections that were open across the delete keep working.ControlSocketServer+File.swift, which keeps the server file under its 400-line budget. The stale-socket probe now setsSO_NOSIGPIPE, so the crash-safety check passes for that file.plans/cmux-next/idle-wakeups.mdgets a row for the new wake source.Testing
New suite
ControlSocketPathRestoreTests, driven through the real Unix socket on the fleet (run-swift-testing-suites.sh Packages/macOS/CmuxNext, Xcode 26.6):aDeletedSocketFileComesBackWithItsMode: deletes the file under a running server and expects:PONGwithin 5 s;It then deletes the file again, and finally checks that
stopremoves it.aPathAnotherServerTookIsLeftAlone: a plain listener's socket is renamed over the path. The test expects the path to keep that inode, including after the first server stops.aStopDuringARestoreClosesTheNewSocket: the restore's bind callsstop().restoresThatKeepFailingBackOffAndStillComeBack: binds fail after the delete. The test expects at most 12 attempts in 2 s, then aPONGonce binds work again.PONGwithin 10 s.Local checks:
check-concurrency.sh,check-crash-safety.sh,crash_ratchet.pyandcheck-no-godfiles.shshow nothing new.Changelog
Fixed: cmux NEXT's control socket comes back on its own when something deletes the socket file, so the CLI reconnects without relaunching the app
Proof
No UI. The socket-level tests above are the proof.
🤖 Generated with Claude Code
Need help on this PR? Tag
@codesmith-botwith what you need.Summary by cubic
If the control socket file is deleted while cmux NEXT is running, the app now rebinds a socket at the same path instead of keeping the listener on the unlinked inode, where no client could connect until a relaunch. The readiness soak (hq#1870) hit this when an external
/tmpsweep removed/tmp/cmux-debug-<tag>.sock.ControlSocketPathWatchwatches the socket's directory with kqueue (no timer) and runs its check inline on the accept queue, so a restore never races an accept pausing on descriptor exhaustion.renamex_np(..., RENAME_EXCL)), so a path another server or a file took is never replaced.stoplanding mid-restore cancels it and removes its file. After three failed restores in a row the watch ignores events and retries with a backoff capped at 60 s until one works.stopstill unlinks only this server's own inode, and connections open across the delete keep working.ControlSocketServer+File.swift; the stale-socket probe setsSO_NOSIGPIPEas the crash-safety check requires.ControlSocketPathRestoreTestsdrive the real Unix socket: restoring the file with its mode, leaving a path another server took alone, closing a new socket when a stop lands mid-restore, and backing off when restores keep failing. The test client stores its descriptor only after a connect succeeds, so a failed connect'sdeinitnever closes a descriptor another suite's listener holds.Written for commit 2358d24. Summary will update on new commits.