acpmux: a DEV build's daemon stops once its app is gone
Summary
The app starts its acpmux daemon detached so the daemon outlives the app (its sessions are durable state). For a throwaway tagged DEV build that meant forever: scripts quit DEV apps or delete their bundles without stopping the daemon, which then runs under launchd and idles at about 10% of a core. On one Mac on 2026-10-21, 23 such daemons from dead DEV builds held about two cores (part of a load average near 400). The dead app's headless owners stay connected to the daemon, so no "no client" rule can fire.
A DEV daemon now stops itself through its ordinary shutdown (hosted agents are kept for the next daemon, as on SIGTERM) once its app is gone:
- Only when the bundle around the daemon's executable is
com.cmuxterm.app.debug[.<tag>](read from its Info.plist at start), on macOS, and the app named its control socket inCMUX_SOCKET_PATH. - Every 10 minutes it connects to that socket and closes at once. The app counts as gone when the socket refuses after it answered at least once, or when the daemon's own executable was deleted (the tag bundle was removed). After an hour gone, the daemon shuts down. An app that never listens (socket control off, a socket that failed to start) keeps its daemon. This is a socket connect, not a process scan (
check-no-process-scan). - The bundle is read before the ready line, so a bundle deleted right after launch still counts.
- Release, NIGHTLY and Cloud daemons never start the watcher. Same shape as the headless owner's
dev_orphan_exit.rs.
Known gaps (review notes, left as is):
- If the app's socket is not up at the daemon's first try and the app quits before the next one (10 minutes later) while its bundle stays, the daemon still never stops. The app binds its socket before it opens an agent pane, so the first try normally succeeds.
- A moved or renamed DEV bundle counts as deleted.
- The orphaned headless Chief server and
optchat-chief hostof a dead DEV app are not stopped here; they need their own owner signal (follow-up).
Testing
Regression test committed before the fix (tests/dev_orphan_exit.rs): a daemon run from a fake cmux DEV bundle with the debug-only short intervals and a listener standing in for the app's socket.
Command: cmux-tui.yml focused mode, test_filter=dev_orphan (the dispatch scripts/verify-cmux-tui-hosted.sh --filter dev_orphan makes), on hosted Linux and macOS.
- Test-only head
0e584061a4d2, red on macOS as expected: https://github.com/manaflow-ai/cmux/actions/runs/39579592112.dev_orphan_exit_stops_a_dev_daemon_after_its_app_quitsfailed ("the DEV daemon kept running 20s after its app quit") and so diddev_orphan_exit_stops_a_dev_daemon_whose_bundle_is_deleted;..._keeps_..._app_never_listensand..._never_stops_a_nightly_daemonpassed (2 passed, 2 failed). - Fix head
8518234c1c04(fix, a merge of currentfeat-cmux-next, Linux clippy fix): https://github.com/manaflow-ai/cmux/actions/runs/39629890886. green on macOS: all fourdev_orphan_exit_*integration tests and all sixdaemon::dev_orphan::tests::*unit tests passed;test (linux)passed. That job's own red is only the base clippy error incrates/cmux-tui/tests/vm_child_sessions_journey.rs:170(std::slice::from_ref), which this PR does not touch. The first macOS attempt was refused by its runner for disk space and was rerun on the same SHA. - On both,
test (linux)passed (the Linux tests: NIGHTLY and off-macOS keep serving, plus the unit tests). Both macOS jobs were still waiting for a hosted runner after an hour.
Review: a correctness review subagent returned FIX (an app that never listens would lose its daemon), fixed in this head, then PASS on the re-review.
Observed / Not run
- Observed on 2026-10-21: 23 orphaned DEV daemons, each with its app gone, at about 10% CPU each. Each app daemon's environment names its app's control socket in
CMUX_SOCKET_PATH, and that socket is gone once the app quits. python3 scripts/cmux-next/prepush-gate.py: no new violations.lint (linux)failed before the merge on clippyunnecessary_sort_byincmux-worktree-care/src/store.rs:97, a file this PR does not touch. The base tip has already fixed it, and this head merges that fix. A rustfmt import order in the new test was also fixed. The first hosted run (https://github.com/manaflow-ai/cmux/actions/runs/39563749392) got no macOS runner in 2 h.- The fleet's
cmux-ci runrefusedscripts/ci/cmux-tui-focused-test.shandscripts/ci/cargo-test-selected.sh(not on the ci-step allowlist), so the macOS tests wait for the hosted runner. - Not run: no local cargo (repo rule); no tagged DEV app dogfood. The behavior is DEV-only and covered by the integration test with a stand-in socket.
Changelog
none
š¤ Generated with Claude Code