Skip to content

cmux NEXT: put the control socket back when its file is deleted under a running app (hq#1870) - #20471

Merged
teamleaderleo merged 68 commits into
feat-cmux-nextfrom
continuity-soak-socket-rebind
Oct 14, 2026
Merged

teamleaderleo merged 68 commits into
feat-cmux-nextfrom
continuity-soak-socket-rebind

Conversation

@teamleaderleo

@teamleaderleo teamleaderleo commented Oct 14, 2026 •

Copy link
Copy Markdown
Collaborator

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>.sock at 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:

  • ControlSocketPathWatch watches the socket's directory with kqueue, with no timer. On each directory change it runs one lstat on 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.
  • The new socket is bound the same way as the first one: in a private staging directory, with its mode set, then renamex_np(..., RENAME_EXCL). A path that another server or a file took in the meantime is never replaced.
  • The new listener is resumed before it is published. If a stop lands mid-restore, the new listener is resumed, cancelled, and its file removed.
  • A restore that keeps failing would trigger itself again, because its bind stages a directory beside the path. After 3 failures in a row, the watch ignores events and retries on a DemandTimer with Backoff from 1 s to 60 s until a restore works.
  • stop still unlinks only this server's own inode, and connections that were open across the delete keep working.
  • The socket-file helpers move unchanged into ControlSocketServer+File.swift, which keeps the server file under its 400-line budget. The stale-socket probe now sets SO_NOSIGPIPE, so the crash-safety check passes for that file.
  • plans/cmux-next/idle-wakeups.md gets 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:

    • a new connect gets PONG within 5 s;
    • the file is back as a socket with the access mode's permissions;
    • an open connection keeps working.

    It then deletes the file again, and finally checks that stop removes 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 calls stop().

    • Red at fd614cb: the test process crashed in libdispatch (a never-resumed source was cancelled and released).
    • Green.
  • restoresThatKeepFailingBackOffAndStillComeBack: binds fail after the delete. The test expects at most 12 attempts in 2 s, then a PONG once binds work again.

    • Red at 1e04356: the watch had stopped for good, so there was no PONG within 10 s.
    • Green at 8dd3953: 3.3 s, and every suite passed.

Local checks: check-concurrency.sh, check-crash-safety.sh, crash_ratchet.py and check-no-godfiles.sh show 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


View with [code]smith Autofix with [code]smith
Need help on this PR? Tag @codesmith-bot with 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 /tmp sweep removed /tmp/cmux-debug-<tag>.sock.

  • ControlSocketPathWatch watches 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.
  • It asks the server for a new listener only when nothing is at the path; the socket is bound like the first (staging dir, mode, renamex_np(..., RENAME_EXCL)), so a path another server or a file took is never replaced.
  • The new listener is resumed before it is published; a stop landing 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. stop still unlinks only this server's own inode, and connections open across the delete keep working.
  • Socket-file helpers moved to ControlSocketServer+File.swift; the stale-socket probe sets SO_NOSIGPIPE as the crash-safety check requires.
  • New ControlSocketPathRestoreTests drive 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's deinit never closes a descriptor another suite's listener holds.

Written for commit 2358d24. Summary will update on new commits.

View guided diff Turn on auto-fix

lawrencecchen and others added 3 commits October 13, 2026 22:56
…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>
@coderabbitai

coderabbitai Bot commented Oct 14, 2026 •

Copy link
Copy Markdown

Important

Review skipped

Auto reviews are disabled on base/target branches other than the default branch.

Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration
  • Configuration used: Repository: manaflow-ai/cmux/.coderabbit.yaml
  • Review profile: ASSERTIVE
  • Plan: Advanced
  • Run ID: 4abc392d-f1c8-4ca9-be2f-75e27644bb89

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review
  • Autofix · Keep fixing CodeRabbit findings and required CI, and resolving merge conflicts

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.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@teamleaderleo

Copy link
Copy Markdown
Collaborator Author

Bead cx-s5fig created for this PR by the cmux coordinator.

@github-actions

github-actions Bot commented Oct 14, 2026 •

Copy link
Copy Markdown
Contributor

Not verified: macOS jobs did not run: compile and app tests were skipped on 2358d24ed6; ci-status is green without them.

CI passes on 2358d24ed6 (run 38562971242 attempt 1).
macOS jobs did not run: compile and app tests were skipped: Fast static checks skipped (the macos job needs both to succeed). Re-run CI once they pass, before merging.

Written by scripts/ci/classify_failures.py (ci-failure-attribution.yml); signatures are its SIGNATURES table. A machine verdict is the runner's fault, not this PR's; yours means the failing file is one this PR changes, also red on main that main's latest full suite fails the same way, seen on other PRs that it failed on another pull request's run lately.

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>
@cursor

cursor Bot commented Oct 14, 2026

Copy link
Copy Markdown

Bugbot is paused — on-demand spend limit reached

Bugbot 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>
@cursor

cursor Bot commented Oct 14, 2026

Copy link
Copy Markdown

Bugbot is paused — on-demand spend limit reached

Bugbot 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.

teamleaderleo and others added 3 commits October 13, 2026 23:57
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>
@blacksmith-sh

This comment has been minimized.

teamleaderleo and others added 3 commits October 14, 2026 00:48
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>
@github-actions

github-actions Bot commented Oct 14, 2026 •

Copy link
Copy Markdown
Contributor

cmux-next tour of 2358d24ed6731a8b10d6c3409858a066c79883ba (run)

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 scripts/cmux-next/media/tours: add a step or a tour file to show your feature.

lawrencecchen and others added 11 commits October 14, 2026 02:49
… 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)
… as sent (the socket deadline is now 6 s) (cx-d0d.97)
lawrencecchen and others added 20 commits October 14, 2026 04:01
…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>
@cursor

cursor Bot commented Oct 14, 2026

Copy link
Copy Markdown

Bugbot needs on-demand usage enabled

Bugbot 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.

@teamleaderleo

Copy link
Copy Markdown
Collaborator Author

Review notes (non-blocking), carried into the follow-up socket-move PR #20508:

  1. ControlSocketPathWatch.start() does nothing when open(parent, O_EVTONLY) fails. cmux-next: keep the control socket in ~/.local/state/cmux, with a /tmp link #20508 will log that once so a dead watch is visible.
  2. On /tmp every directory write by any process costs one lstat on the accept queue. Dispatch coalesces these, and the idle-wakeups row already records the cost. cmux-next: keep the control socket in ~/.local/state/cmux, with a /tmp link #20508 moves the socket to a per-user state folder, which keeps that directory quiet.

🤖 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>
@teamleaderleo
teamleaderleo temporarily deployed to cmux-tui-merge-tree October 14, 2026 12:19 — with GitHub Actions Inactive
@teamleaderleo
teamleaderleo merged commit e12f567 into feat-cmux-next Oct 14, 2026
122 of 137 checks passed
@teamleaderleo
teamleaderleo deleted the continuity-soak-socket-rebind branch October 14, 2026 13:53
teamleaderleo added a commit that referenced this pull request Oct 14, 2026
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>
teamleaderleo added a commit that referenced this pull request Oct 14, 2026
…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>

This branch was previously deployed

1 inactive deployment
cmux-tui-merge-tree — 2358d24e Deployed Oct 14, 2026 by teamleaderleo via publish the merge tree #661
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants