Found by adversarial review round 3 of PR #109 (option (b) session cancel, ADR 0011). Severity P2, not a regression versus main.
cmd.Wait() reaps the process via Process.Wait() first and only then waits for the output copiers; container.reap() runs after the whole Wait returns. Probe: set -m; sleep 600 & …; exit — the shell and its original process group are gone from the kernel while the container still reports reaped=false and holds the old PGID. A cancel landing in that window sends the one allowed SIGKILL to a PGID that may already have been reused.
Suggested fix: tie the "reaped" mark to the actual kernel reap (observe exit without immediately reaping, or keep a separate group-lifetime holder process), not to the return of cmd.Wait().
Probes: /tmp/wanctl-pr109-round3/probe_test.go (may not survive reboot); the review report is in the ledger docs/plans/2026-09-18-webfetch-skill-duration-issues.md.
Found by adversarial review round 3 of PR #109 (option (b) session cancel, ADR 0011). Severity P2, not a regression versus main.
cmd.Wait()reaps the process viaProcess.Wait()first and only then waits for the output copiers;container.reap()runs after the whole Wait returns. Probe:set -m; sleep 600 & …; exit— the shell and its original process group are gone from the kernel while the container still reportsreaped=falseand holds the old PGID. A cancel landing in that window sends the one allowed SIGKILL to a PGID that may already have been reused.Suggested fix: tie the "reaped" mark to the actual kernel reap (observe exit without immediately reaping, or keep a separate group-lifetime holder process), not to the return of
cmd.Wait().Probes: /tmp/wanctl-pr109-round3/probe_test.go (may not survive reboot); the review report is in the ledger docs/plans/2026-09-18-webfetch-skill-duration-issues.md.