fix(session): kill leaked provider process trees - #8276
Conversation
Open PR relationship auditThis is a consolidated, point-in-time code-level audit note. It compares complete merge-base diffs and current/merged code; it does not treat a shared topic as duplication or partial coverage as completion. Relationship findings
No PR, Issue, label, branch, or review state was changed by the relationship-note portion of this audit. |
56286f8 to
cb43261
Compare
|
Rebased onto main Clean rebase, no conflicts. The one extra change: Gates run locally on the changed files only: black, isort, flake8 all clean; Please review the resolution. A maintainer push makes the maintainer the last pusher, so under the repo's last-push rule a second approver is needed. Reply if anything looks wrong. |
Design Review (Fable 5, fork) — 🟡 CONCERNSDesign-level review of I have everything I need. The core premise is verified (ACP providers spawn with Design-Verdict: CONCERNS Right layer and right fix, but the TERM→KILL escalation is still keyed on launcher-PID liveness, so a narrow window of the original leak survives. WatchThe escalation loop skips SIGKILL exactly when it's needed: after Suggestions
[DESIGN-REVIEWED] cb43261 |
First Principles Review (Fable 5, fork) — 🟡 CONCERNSPremise-level review of All facts are verified. The fix is mechanism-level, uses the existing repo-wide First-Principles-Verdict: CONCERNS The harvested rule already has a counted unfixed sibling: What this change shipsIntent: stop a discarded provider's sandbox launcher from orphaning its kiro-cli/MCP descendants at teardown — a FIX. Inventory (4 items)
Watch
[FIRST-PRINCIPLES-REVIEWED] cb43261 |
Opus 4.8 Review (fork) — ✅ no blocking findingsReviewed Review detailsNo findings. The sole candidate relies on a reap race between the SIGTERM iteration's non-blocking [OPUS-REVIEWED] cb43261 |
GPT 5.6 Review (fork) — 🔴 changes requested (blocking)Reviewed 2 of 2 blocking finding(s) are security-class and were withheld from adjudication, so the blocking verdict stands. BLOCKING -- src/kiro_crew/session_pid.py:637 -- escalation loses the process group when the launcher exits BLOCKING -- test/test_pid_lifecycle.py:1066 -- file-writing child inherits the repository working directory [BLOCK-MERGE] cb43261 Adjudication (Opus 4.8) — is blocking on each finding proportionate?Both findings are FENCED (annotate-only); the adjudicable block is empty. F1 — session_pid.py:637, escalation loses the process group when the launcher is reaped between SIGTERM and SIGKILL. F2 — test_pid_lifecycle.py:1066, file-writing child inherits the repo working directory. 🏷️ Fenced finding(s) machine-flagged as likely edge caseThe security fence keeps these findings blocking regardless of adjudication; the only clearance path is a human override recorded by a repository writer, who must independently verify a rationale before recording it — it is machine-authored, and a wrong override on a security-class finding ships exactly the class the fence exists to stop. (This lane's comment deliberately carries no override command.)
|
Problem / Motivation
The leaked-provider fallback could leave a
kiro-cliruntime alive after its provider object was discarded. On sandboxed launches, the PID exposed by the provider can be a launcher whose descendants include the real runtime and MCP processes._sync_kill_providersignalled only that launcher PID, so the launcher exited while descendants were reparented to init and escaped every provider teardown path.The existing orphan detector then reported a managed runtime carrying the spawn marker but present in neither PID file, because the discarded provider had already lost the ownership handle needed to reclaim that tree.
Why it matters
Each escaped agent runtime keeps its process tree and memory alive until manual cleanup or host restart. Repeated provider discards can therefore accumulate invisible background processes and progressively consume host resources.
What changed (motivation → approach → change)
The fallback already has a positively identified provider PID and exists specifically to reap a leaked provider. Instead of signalling only that PID,
_sync_kill_providernow routes both POSIX TERM/KILL escalation and the Windows hard-kill path throughplatform_compat.kill_process_tree.On POSIX, the provider launcher is created as an isolated process-group leader, so group signalling reaches the launcher and descendants even after the launcher exits during TERM grace. On Windows, the existing compatibility helper uses recursive process-tree termination. Invalid/sentinel PIDs and existing error handling remain unchanged.
The audit message now says that a process tree was killed, matching the operation performed.
Tests
_sync_kill_providerunit coverage to requirekill_process_treefor POSIX escalation, already-dead processes, Windows cleanup, and invalid PID rejection.test/test_pid_lifecycle.py+test/test_session_pool.py: 247 passed.Manual verification
N/A — the regression test exercises a real isolated launcher/process group and a child that survives TERM, which covers the process-lifecycle behavior directly.
Related Issues
no linked issue: the detector introduced during #2930 exposed this additional teardown path after that investigation closed.
Pattern harvest
Rule candidate: review-prompt
Pattern: a lifecycle fallback that owns a launcher PID must reap the isolated process tree, not only the launcher process.
Checklist
feat|fix|docs|refactor|perf|test|chore|ci|build|revert: ...)