acpmux: an idle harness ends when its MCP server's path has a space in it or is relative
Carries #27558 forward. Its owner is gone, it conflicted with #27681, and the review at 07ebf4d9 BLOCKed it. This PR merges its commits as they were, with their authorship, onto feat-cmux-next, resolves the conflict and fixes the BLOCK.
Idle hibernation (#26127) never ended a harness: every chat's idle pass found its harness "busy". acpmux's own MCP servers run from /Applications/cmux NIGHTLY.app/.... The census split ps's space-joined line into words, so a path with a space never matched a server. #27558's description has the full cause and fix. In short: argv is read word by word (KERN_PROCARGS2, /proc/<pid>/cmdline), and the kernel's executable (proc_pidpath, /proc/<pid>/exe) must match the server's program, or for a script the interpreter its #! line runs. A process that can't be read fails closed.
Leo's words for the stack: "also when it comes to hibernation...can't you hibernate the agent but not hibernate the bg tasks?"
What this PR adds on top of #27558
- Merge with #27681 (npm's Codex launcher relay), and later a base merge past 4154db06. Base's launcher relay (the relayed native program and its
codex-*helpers) now goes by the same identity as servers do. - The BLOCK at 07ebf4d9: a relative server path.
./mcp-server(or a relative#!script) was canonicalized against acpmux's own cwd, so it never matched and its harness never hibernated.- It now resolves in the folder the harness was started in (
LaunchServers::Knownkeeps it). - Only absolute paths are canonicalized.
- A relative script must also be that file from the folder the process itself runs in.
Identitycarries the process's cwd:/proc/<pid>/cwdon Linux,proc_pidinfo(PROC_PIDVNODEPATHINFO)on macOS. Socd /tmp && node ./server.jsis another script.
- It now resolves in the folder the harness was started in (
- Review rounds on this PR (gpt-6.1-sol high, fast), each fixed with a red test on the code before it:
- e53952b2: the relay and helper rules still read the
psline, soexec -acould pass. They now need exact argv plus the kernel's executable (runs_file). - 37d01e14: the launcher was read from the leader's argv alone. The leader's executable must now be the interpreter its argv names.
- 07eb7d46:
- A relative script wasn't tied to the process's folder. It is now (above).
- A bare
nodelauncher matched any executable named node. It must now be thenodethe harness'sPATHruns; with noPATHknown (a harness another daemon started), a bare name is no launcher. - Not changed, and the reviewer answered
HELPER: keep: acodex-*helper must be the very executable in the relayed program's own install folder. The census guards against work the agent ran, not against files written into the harness's install, which could equally replace the harness.
- ac3d4977:
relaysaccepted any program with the launcher's name and args, so/tmp/codex -c ... app-serverbecame the harness. The relayed program's real file must now be under the outermostnode_modulesfolder that holds the launcher script's real file. npm puts the native package there, whether nested or hoisted.
- e53952b2: the relay and helper rules still read the
- The census tests move to
hub/idle_census_tests.rs, sinceidle_census.rswas at its 1000-line cap. The launcher tests now build a real npm tree, in a folder with a space. scripts/cmux-next/process-scan-baseline.tsv: theagent_host/tree.rsrow goes from 5 to 7 for the two folder reads. It stays classremove, with the census.
Tests (CI, Linux, via cmux-ci test: run 41029177009 at ac3d4977 and run 41038865323 at 62f41ee8):
- Red on the code before each fix, by the same test names:
a_relative_server_is_the_one_in_the_harnesss_folder(at b18c55a7):left: false, right: true.- A spoofed relay (at e53952b2) passed as the harness.
- A
nodeoffPATH, and a relative script from another folder (at 07eb7d46), both passed. - At 62f41ee8 with the npm-install check removed: both launcher tests fail on the stray
codexcases.
- Green at 62f41ee8: all 16 census tests (
idle_census,census_tests), including:a_relay_or_helper_is_its_executable_not_its_argv;cwd_is_the_folder_a_live_process_runs_in(a live child, in a folder with a space).
cargo clippy -p acpmux --all-targets -- -D warningsis clean. The panic ratchet's deny lints have no hits in either changed file.
Observed / Not run
- Observed: the runs above;
prepush-gate.pyreports no new violations. - Not run:
- A macOS compile of the
cfg(target_os = "macos")reads. A crosscargo checkstops atring's build script, so this PR's macOS CI is the check. bench=hibernateon a fleet Mac.
- A macOS compile of the
- Reds on this PR that are base reds:
cmux-next checks: "Sidebar look owner" names 3 base commits;prepush-gate.py --only sidebarfails the same way on base and finds 0 in this PR's commits. "String tables" is also red there, and this PR touches no strings.release-notary.
Takes back #27681: its tree::leader_args_in (the leader's ps line) and the (String, Vec<Under>) return of tree::under. The census reads the leader's exact argv and executable through tree::identity instead, because a ps line can be spoofed and splits a path with a space in it. #27681's launcher relay itself stays, now checked by identity.
Review (codex gpt-6.1-sol high, fast): SHIP at 62f41ee8ebeebbd5e91c27fa73665a691f223771. It reviewed the whole diff from the merge base e8630779. The four rounds before it BLOCKed, and each is fixed above.
Risk: a false match would let the idle pass end a harness's process group with a non-server under it. Exact argv, the kernel's executable and failing closed guard against that. A relative path resolves only in the harness's own folder.
No capture: no UI.
Changelog
Fixed: idle cmux NEXT chats hibernate their agent again when an MCP server's path has a space in it or is relative.
Agent session: agent session
š¤ Generated with Claude Code