Skip to content

bug: TerminateCommand reports spurious SIGKILL-timeout when ps is unavailable and leader is a zombie #862

Description

@PierrunoYT

Context

Follow-up from review of PR #774.

internal/background/process_posix.go:47-51 (terminateOwnedProcess) always returns leaderAlreadyExited = false, unlike its Windows counterpart in internal/background/process_windows.go, which computes alreadyExited from GetExitCodeProcess before attempting termination. TerminateCommand (internal/background/terminate.go) uses that flag to decide whether a termination error is safe to suppress once the reap has independently succeeded.

Reproduction

Confirmed empirically (WSL Ubuntu, Go 1.26.5 linux/amd64): start a child as its own process-group leader, let it become a zombie, make ps unresolvable (e.g. PATH=""), then call TerminateCommand.

Result:

TerminateCommand elapsed=406ms err="process <pid> did not exit after SIGKILL"
ProcessState: exit status 0

The reap succeeds cleanly, but TerminateCommand still returns a spurious termination error — because signalTargetRunning (internal/execution/process_unix.go) falls back to the raw kill(pid, 0) result when ps is unavailable or unparseable, and kill(0) succeeds against a zombie. The tree is genuinely already dead, but the code can't distinguish that from "still alive" without ps, so it burns both grace periods and reports failure.

This matters because that error string is surfaced verbatim in zero daemon start's failure message when startAndAwaitDaemonProcess's cleanup path hits it.

Why this isn't a simple fix

Using "the reap succeeded" (or "the leader was already a zombie before termination began") as a stand-in leaderAlreadyExited is NOT safe: a zombie leader can still have a live orphaned child in the same process group (see TestTerminateCommandKillsChildAfterLeaderExits), and only ps//proc can currently tell that group-liveness apart from "unknown." Using the reap result to suppress the error would risk masking that genuinely-alive-descendant case too.

A correct fix likely needs either:

  • A portable way to positively confirm the group has zero non-zombie members without ps (e.g. /proc/<pid>/stat scanning on Linux, a Darwin-specific equivalent, falling back to today's conservative behavior only where no such mechanism exists), or
  • A deliberate, documented decision to accept the rare spurious-failure report as the price of never risking a false "success," with a clearer error message that says the liveness check couldn't run rather than asserting "did not exit after SIGKILL".

Requires design discussion before implementation. Not blocking ps is present on all Unix targets we ship for today.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions