Question
ProcessRunner::capture spawns with OwnGroup::No, SIGKILLs one pid on timeout, and joins the pipe-drain threads after the wait (rust/devlaunch-runner/src/lib.rs:421-434, kill at :818-824). read_to_end returns only at pipe EOF, so a killed child that left a grandchild holding the inherited fd blocks the join forever — Outcome::TimedOut never returns. Reachable in production: clients/git.rs uses capture-with-timeout on five verbs, and git fetch over ssh forks exactly such a grandchild. The suite dodges the shape (exec sleep 30 guarantees a single process).
Decide the fix shape: (a) spawn captures in their own process group and killpg on expiry; (b) bound the drain-thread joins and abandon them on expiry; (c) both. Also decide whether session's reader.join() (lib.rs:561-563) needs the same treatment, and name the failing test that proves the defect (a child that forks a grandchild holding the pipe).
Question
ProcessRunner::capturespawns withOwnGroup::No, SIGKILLs one pid on timeout, and joins the pipe-drain threads after the wait (rust/devlaunch-runner/src/lib.rs:421-434, kill at:818-824).read_to_endreturns only at pipe EOF, so a killed child that left a grandchild holding the inherited fd blocks the join forever —Outcome::TimedOutnever returns. Reachable in production:clients/git.rsuses capture-with-timeout on five verbs, andgit fetchover ssh forks exactly such a grandchild. The suite dodges the shape (exec sleep 30guarantees a single process).Decide the fix shape: (a) spawn captures in their own process group and
killpgon expiry; (b) bound the drain-thread joins and abandon them on expiry; (c) both. Also decide whethersession'sreader.join()(lib.rs:561-563) needs the same treatment, and name the failing test that proves the defect (a child that forks a grandchild holding the pipe).