cloudhv tests SIGKILL the process group when kill guard is mutated
open by a73x
Labels: backlog
[claude 2026-08-22] Found while running gremlins (mutation testing) across the tree.
cloudhv.go:497 reads:
pid := p.ownedPID(vmID)
if pid != 0 {
if err := p.signal(pid, syscall.SIGKILL); err != nil && err != syscall.ESRCH {
Negate that guard — which is exactly what a CONDITIONALS_NEGATION mutant does — and it becomes `p.signal(0, SIGKILL)`. POSIX kill(0, sig) means "every process in my process group", so the test binary SIGKILLs gremlins, the sweep, and the shell that launched it. Reproduced twice; exit 137 with no OOM record.
The mutant only reaches a real syscall because `signal` is not always injected. Most tests set `p.signal = func(...)`, but the Destroy call sites at cloudhv_test.go:169, 294, 568, 591, 632, 654 build the Provisioner with New(...), which wires the real syscall.Kill (cloudhv.go:105).
internal/agent/vfkit has the same shape (vfkit.go:109 `signal: syscall.Kill`) and should be checked the same way — its sweep passed, but only because no mutant happened to reach an uninjected call site.
Fix: inject `signal` in every test that can reach kill()/sigterm(), so no test can ever deliver a real signal. Then internal/agent/cloudhv can rejoin the mutation sweep — it is the ONLY package excluded from the 2026-08-22 baseline.
Reproduce: `make mutate PKG=./internal/agent/cloudhv` (run it under setsid unless you want your shell to die).
Comments
a73x
[claude 2026-08-22] Related landmine, now contained but not fixed: mutate-report in `make ci` runs gremlins under setsid precisely because of this mutant. A kill(0) can still abort the mutation run — it just can no longer take the build, or the operator's shell, with it. The containment is in the Makefile comment; the fix is still this issue.