acpmux: an idle harness ends when its MCP server's path has a space in it
Idle hibernation (#26127) never ended a harness: every chat's idle pass found its harness "busy".
Cause. The idle census matched each process under a harness against its MCP servers, using the line ps prints (argv joined by spaces), and it refused any server whose command or args held a space. acpmux's own MCP servers (forum, offer, secret and so on) run the acpmux binary from the app bundle, /Applications/cmux NIGHTLY.app/Contents/Resources/bin/acpmux. So in the nightly no chat ever hibernated. The fleet bench (bench=hibernate, builds under /Library/Application Support/...) showed it:
- every agent process was still running after 135 s idle;
- with #26928's log lines, every chat logged
idle harness stays up: a process that is not one of its MCP servers runs under it ... comm=Application.
Fix (hub/idle_census.rs, agent_host/tree.rs):
- Exact argv. Each candidate's argv is read word by word:
KERN_PROCARGS2on macOS,/proc/<pid>/cmdlineon Linux. It is compared with the server's argv exactly, so a path with a space in it matches, and the same words split differently never do. - Executable. Argv is the process's own to rewrite (
exec -a,process.title), so the executable the kernel says it runs must match too:proc_pidpathon macOS,/proc/<pid>/exeon Linux.- A direct server's executable must be its program.
- A script server's executable must be the interpreter its
#!line runs (env NAMEresolved on the harness'sPATH), never a file merely namednode.
- Fails closed. A child whose executable or argv can't be read is not a server.
- Bounded. The listing, the reads and the match all run in the census's bounded blocking task.
Tests (Linux, 3 runs, 12/12 each; clippy -D warnings clean):
- spaced paths, including the nightly bundle path;
- argv split differently;
exec -awith a live child;process.titlepadding;- a non-interpreter executable, and a fake
node; - shebang parsing (
env,env -S, direct, none).
After landing, bench=hibernate on a fleet Mac should show the chats' agent processes gone after the 2-minute idle.
Leo's words for the stack: "also when it comes to hibernation...can't you hibernate the agent but not hibernate the bg tasks?"
Risk: a false match would let the idle pass end a harness's process group with a non-server under it. Exact argv plus the kernel's executable, failing closed, is what guards it. The reviews below went four rounds on exactly this.
No capture: no UI.
Review (codex gpt-6-astra high, fast): SHIP at 5357a148a5824878a925e0e9c99f4cdd24ebf894. Rounds 1-3 BLOCKed, all fixed:
Review (codex gpt-6-astra high, fast): SHIP at 01bab617b94d64574d98c772bf522ea6f3adad4c. This is a delta review of a base merge, whose conflict with base's panic-ratchet cleanup in runs() was resolved with get() in place of indexing. It also covers #[cfg(unix)] on runs(), added for the Windows GNU build, which a Linux cross-check confirms. 12/12 census tests pass, clippy is clean, and the crash ratchet adds no acpmux hits.
Review (codex gpt-6-astra high, fast): SHIP at 07ebf4d91c99fdc5e272fb1bf41fc9b79e2e817d. This is a delta for the fast-gates fixes:
- a test path no longer names a literal state root;
scripts/cmux-next/process-scan-baseline.tsv's row foragent_host/tree.rsgoes from 1 to 5, still classremovewith the census. The census now reads each child's argv (KERN_PROCARGS2,/proc/<pid>/cmdline) and executable (proc_pidpath,/proc/<pid>/exe) next to the existingpslisting.
- Whitespace-normalized argv could match a different split.
- Argv alone could be spoofed, so the executable is now checked.
- Any file named like an interpreter passed, so the script's
#!interpreter is now required.
Agent session: agent session
š¤ Generated with Claude Code