Repository navigation
Description
Summary
An ACP agent process is ended only by AcpClient.close(). When the Raven process that launched it exits without closing the connection pool -- killed, crashed, or ended by the OOM killer -- the agent keeps running with no host left to talk to, and keeps its memory until it is killed by hand.
A Linux-only change is in preparation: a parent-death signal on the launched process, and a sweep at the next ACP launch that ends the process groups recorded by hosts that are no longer running. This issue tracks macOS and Windows, which that change does not cover.
Evidence
Observed on a Linux host on 2026-10-12: two third-party ACP agents (codebuddy --acp, qodercli --acp) started by the connection test (ping_agent) on 2026-09-20 were still running 22 days later, reparented to init, with 523 MiB PSS combined. Before #554 the connection test held its connection in the host's shared pool, and the host's exit did not close it.
Reproduced on Linux at main (182daca) with a stand-in host that calls AcpClient.launch and a stand-in agent that does not exit on stdin EOF:
1. host: await AcpClient.launch(name="stub", command="python agent.py")
2. shell: kill -9 <host pid>
3. agent: still running, ppid 1, pgid == sid == its own pid
Root cause
raven/acp_client/client.pystarts every agent withstart_new_session=True, so the agent receives none of the host's signals, and ends it only inclose()(os.killpg(pgid, SIGKILL)).- Hosts close the pool on their teardown paths (
close_pool()in the gateway, TUI and serve teardowns). A host that dies before reaching them leaves every live ACP agent running. - An agent that exits on stdin EOF still ends when the host's pipe closes; one that does not, stays.
Platform notes
- macOS: there is no
PR_SET_PDEATHSIG, so the Linux parent-death signal does not apply, and a sweep needs a process start time from somewhere other than/proc(for examplesysctlwithKERN_PROC_PID) to tell a recorded group leader from a reused PID. - Windows:
start_new_sessionis ignored and there is noos.killpg. Closing a connection cleanly is addressed in fix: restore bundled ACP agent startup on Windows #908 (taskkill /T /Fwhile the launcher is alive). For a host that exits without closing, the platform mechanism is a Job Object withJOB_OBJECT_LIMIT_KILL_ON_JOB_CLOSEassigned at launch: every process in the job is terminated when the last handle to the job closes, which includes the owning process exiting.
Expected behavior
An ACP agent does not outlive the Raven process that launched it, whether that process exits cleanly or not, on every supported platform.
Environment
Code path; affects macOS and Windows. The observation and reproduction above are from Linux, Python 3.13, main at 182daca.
Activity
akushonkamen commentedon Oct 13, 2026
Confirmed the root cause:
AcpClient.launch()at raven/acp_client/client.py:261 starts the agent withstart_new_session=True, and the only teardown path isclose()(client.py:301-315) which callsos.killpg(..., SIGKILL)— so any host death that bypassesclose_pool()(kill -9, OOM, crash) orphans the agent. I'm working on a macOS/Windows orphan sweep (record pgid + owner pid + process start time at launch, sweep stale agents before the next launch; Job Object on Windows). Disclosure: this work is assisted by an AI coding agent.akushonkamen commentedon Oct 13, 2026
Confirmed the root cause at main (182daca):
raven/acp_client/client.py:261launches every agent withstart_new_session=True, so the agent receives none of the host's signals, and the only kill path isos.killpginclose()(client.py:315);close_pool()(raven/acp_client/pool.py:615) is only reached on graceful teardown, so a host that is killed or crashes leaves orphaned agents — matching the 22-day, 523 MiB observation. Working on the macOS half (a start-time-guarded sweep viasysctlKERN_PROC_PIDat the next launch); Windows (Job Object withJOB_OBJECT_LIMIT_KILL_ON_JOB_CLOSE) will be included as far as CI allows. Assistance from Claude (AI) acknowledged per repo convention.akushonkamen commentedon Oct 13, 2026
PR opened: #930 (includes the test evidence and the Windows untested declaration). Claim + AI disclosure in #928 (comment)
akushonkamen commentedon Oct 13, 2026
已修复并通过独立验收:修复分支推为 PR,包含根因分析、按进程启动时间判定的孤儿清扫与 Darwin 红绿端到端证据,详见 #931
akushonkamen commentedon Oct 13, 2026
Opened #932 with a launch-time orphan sweep: every launch records (pgid, owner pid, process start time) into a registry under the raven data directory, and the next launch reaps recorded groups whose owner is dead and whose start time still matches (darwin via libproc PROC_PIDTBSDINFO, linux via /proc//stat field 22, so recycled pids are not killed). Record/sweep are best-effort and never block launch. Windows is not implemented — the Job Object design is in the module docstring. Verified red/green on darwin arm64: a kill -9'd host leaves the agent reparented to init without the fix; with the fix, hostB's next launch kills the orphaned group.
fix(*): end leftover ACP agents, trim the heap, and bound the session…
fix(acp_client): end Windows agents when their host exits (#951)
fix(acp_client): end an agent with its host on macOS and sweep what a…
fix(acp_client): end an agent with its host on macOS and sweep what a…
LivXue commentedon Oct 15, 2026
Resolved on main by #951 (Windows Job Object lifetime management) and #953 (macOS host-exit watcher and leftover-agent sweep). Both pull requests were merged on 2026-10-15 and include verification of agent cleanup after abrupt host termination.