fix(runner): destroy ACP session on run completion — orphaned claude-agent-acp process leak#5102
Conversation
…agent-acp leak Claude-Session: https://claude.ai/code/session_01FNiAiGuzfi1kkWvXPPcrVw
|
@coderabbitai review |
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
|
No actionable comments were generated in the recent review. 🎉 ℹ️ Recent review info⚙️ Run configurationConfiguration used: Organization UI Review profile: CHILL Plan: Pro Plus Run ID: 📒 Files selected for processing (1)
📝 WalkthroughSummary by CodeRabbit
WalkthroughThe change modifies ChangesSandbox agent session cleanup
Estimated code review effort: 3 (Moderate) | ~20 minutes Sequence Diagram(s)sequenceDiagram
participant runSandboxAgent
participant PendingApprovalPauseController
participant Sandbox
participant ACPSession
runSandboxAgent->>Sandbox: createSession()
Sandbox->>ACPSession: spawn ACP adapter subprocess
alt HITL pause triggered
PendingApprovalPauseController->>runSandboxAgent: set sessionDestroyRequested=true
PendingApprovalPauseController->>ACPSession: session/cancel
end
runSandboxAgent->>runSandboxAgent: finally block
alt session exists and not sessionDestroyRequested
runSandboxAgent->>Sandbox: destroySession(session.id)
Sandbox->>ACPSession: session/cancel
end
runSandboxAgent->>Sandbox: destroySandbox()
🚥 Pre-merge checks | ✅ 5✅ Passed checks (5 passed)
✨ Finishing Touches📝 Generate docstrings
🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
✅ Action performedReview finished.
|
Context
The OSS team's shared dev runner leaked one orphaned
claude-agent-acpNode process per completed agent run. Under steady automated traffic (~1 run/min), this accumulated to 304 orphaned processes and 5.2 GB RSS in about six hours, and contributed to a machine-wide memory incident on the box (62 GB total, 60 GB used, swap full at 31 GB).Root cause: each run boots a local
sandbox-agentdaemon, which spawns aclaude-agent-acpchild process to drive the harness. Teardown only sends SIGTERM/SIGKILL to the daemon. The daemon does not cascade that kill to its ACP child, so the child reparents to PID 1 inside the container and never exits. The graceful shutdown path, an ACPsession/cancelviadestroySession, was only ever sent on the HITL-pause path. Normal completions and error paths went straight from a live session todestroySandbox(), which hard-kills the daemon but leaves the ACP child behind.Changes
services/runner/src/engines/sandbox_agent.ts: the run'sfinallyblock now sendsdestroySession(session.id)(ACPsession/cancel) beforedestroySandbox(), so the harness gets a chance to exit its ACP child cleanly on every completion path, not just the pause path. A newsessionDestroyRequestedflag guards against a redundant double-send when the HITL pause controller already cancelled the session.Before:
finallyblock calledmcpAbort.abort()→closeToolMcp()→destroySandbox()→dispose(), with no session cancel on normal/error completion.After:
finallyblock callsmcpAbort.abort()→closeToolMcp()→destroySession(session.id)(skipped if already sent) →destroySandbox()→dispose().Scope / risk
One file, teardown path only. No change to run semantics, streaming, or tracing. The only behavior change is an extra best-effort ACP cancel call on teardown; it's wrapped in
.catch(() => {})so a harness that already exited is a no-op.This is a mitigation, not a guarantee. The robust fix belongs upstream in
rivet-dev/sandbox-agent: the daemon should either forward its own kill signal to children or run them in a process group it can kill together, so the ACP child cannot outlive the daemon even under a hard SIGKILL. An upstream issue draft exists but hasn't been filed yet.Tests / notes
pnpm testinservices/runner(556 tests) passes.How to QA
Prerequisites: any stack with the runner and the
claudeharness running (local dev stack or the shared dev box).Steps:
docker exec <runner-container> ps -o pid,ppid,args | grep claude-agent-acp.Expected result: no
claude-agent-acpprocesses withppid=1accumulate after runs complete. Before this fix, every completed run left one such orphan behind (confirmed during the incident: 304 orphans / 5.2 GB RSS after ~6 hours of automated traffic).Automated tests:
Edge cases: the HITL-pause path already called
destroySession; confirm a paused-then-resumed-then-completed run doesn't double-cancel (guarded bysessionDestroyRequested, exercised in the test suite).https://claude.ai/code/session_01FNiAiGuzfi1kkWvXPPcrVw