acpmux: lift Claude and Codex subagent limits for cmux's chats; the idle reaper spares background shells
Chats acpmux starts no longer hit Claude Code's or Codex's subagent caps. An idle chat with a dev server still running in the background is no longer reaped after 5 minutes, which used to take the server down with it.
What changes
Limits lifted at spawn. Every session spawn goes through agent::harness_command: direct, hosted and pooled. So a new chat gets these without a restart. A value the user set always wins.
| Harness | Knob | Was | Now | User value that wins |
|---|---|---|---|---|
| Claude Code | CLAUDE_CODE_MAX_CONCURRENT_SUBAGENTS | 20 | 1000 | profile env, login env, daemon env |
CLAUDE_CODE_MAX_SUBAGENTS_PER_SESSION | — | 100000 | ″ | |
CLAUDE_CODE_MAX_SUBAGENT_SPAWN_DEPTH | 3 | 16 | ″ | |
CLAUDE_CODE_MAX_TOOL_USE_CONCURRENCY | 10 | 64 | ″ | |
BASH_MAX_TIMEOUT_MS (how long a background command may run unattended) | 2 h | ~24.8 days (Claude's own ceiling) | ″ | |
| Codex app-server | agents.max_concurrent_threads_per_session | 6 | 1000 | -c in the profile argv; or the key in codex's config.toml (profile CODEX_HOME first), in its active profile, or in the project's .codex/config.toml |
agents.max_depth | 1 | 16 | ″ |
Three things stay as they are:
BASH_DEFAULT_TIMEOUT_MS(30 min) is unchanged. Raising it would hold every foreground command that long.- Codex's limit of 64 unified-exec processes per session is compiled in.
- Model probes and
cli/harness*launches have no session, so they get no lift.
The lifted limits also apply to chats driven from the Web or a remote relay (coordinator, 10-14). Nothing here bounds in-process subagents' API spend; spend accounting lives in subrouter, not acpmux.
Idle reaper.
- When a shell goes to the background, Claude's translator now sends a
background_task_updatewith staterunning. - The hub tracks running shells per chat, including a subagent's shells, and keeps them across turns. It never reaps a chat with one running.
- When the hub adopts a running host, it rebuilds the set from the log since the harness last started or exited.
- The web client ignores the
runningstate.
No resource valve yet. An earlier revision added a per-chat memory and CPU budget that read ps and /proc. cmux-next never scans processes (plans/cmux-next/pty-holder.md §4, check-no-process-scan), so I took it out. The valve comes back without scanning in cx-v9s8g:
- Linux: a per-session cgroup scope at spawn.
- macOS: the harness in its own process group, lowered with process-group
setpriority. - Once cx-t2sqm.3 lands: rusage reported by the holder.
Evidence
Each test went red, then green, with the same focused hosted command (scripts/verify-cmux-tui-hosted.sh --filter <name>):
| Test | Red on d9a65ff (tests only) | Green on 2c45bbc |
|---|---|---|
Claude knobs (a_chat_starts_claude…) | 38567379348: every knob null | 38555851737 |
Codex -c, with profile-argv and project-config values winning (a_chat_starts_codex…) | 38555281752: no -c overrides | 38555851737 |
| Idle reaper: the chat's own shell, a subagent's shell, and after a later turn | 38555282067: no running update | 38555851478 |
The exact head after the base merges is 71a4aaa. It runs compile, clippy, the fork_through::background_task:: suite and the Codex test in 38586236195 (fork_through::background_task::) and 38586235320 (a_chat_starts_codex).
Locally on the merged head:
check-no-process-scan, the crash ratchet andcheck-no-godfiles.sh --only rustall pass.agent.rsandhub/mod.rsare held at the base's length.- Three review passes found issues that are fixed here: correctness, safety, and a follow-up on the fixes. They covered a subagent's shells, the per-turn reset, codex config precedence, and clippy.
The follow-up design that lists, tails and kills these processes is #20550.
Changelog
- Changed: Chats started by cmux run Claude Code and Codex with their subagent and background limits lifted, and an idle chat with a running background shell is no longer ended.
🤖 Generated with Claude Code