a73x

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   2026-08-22 16:20

[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.