gastownhall/beads · error

procid: terminate process: %w

Error message

procid: terminate process: %w

What it means

Handle.Signal called windows.TerminateProcess and it failed. Before returning the error, the code re-checks liveness: if the process actually exited in the meantime (errProcessExited), termination's goal is met and nil is returned; only genuine termination failures (access denied, handle invalid for another reason) are wrapped with this message.

Source

Thrown at internal/procid/procid_windows.go:87

		return nil, fmt.Errorf("procid: process %d does not match token", pid)
	}
	return &Handle{process: process, token: tok}, nil
}

func (h *Handle) Signal(os.Signal) error {
	if err := h.verify(); err != nil {
		if errors.Is(err, errProcessExited) {
			// The target exited on its own after Open; termination's goal is
			// already met.
			return nil
		}
		return err
	}
	if err := windows.TerminateProcess(h.process, 1); err != nil {
		if _, exitedErr := tokenForProcess(h.process); errors.Is(exitedErr, errProcessExited) {
			return nil
		}
		return fmt.Errorf("procid: terminate process: %w", err)
	}
	return nil
}

func (h *Handle) Kill() error { return h.Signal(os.Kill) }

func (h *Handle) Close() error {
	if h.process == 0 {
		return nil
	}
	err := windows.CloseHandle(h.process)
	h.process = 0
	if err != nil {
		return fmt.Errorf("procid: close process handle: %w", err)
	}
	return nil
}

View on GitHub (pinned to 71377f2769)

Solutions

  1. Check errors.Is(err, windows.ERROR_ACCESS_DENIED); run elevated or take ownership of the target process lifecycle
  2. Confirm the process wasn't protected by security software and adjust policy or target the right process
  3. Re-open with Open and retry once, in case the handle became stale
  4. If termination is best-effort, treat non-access-denied failures as fatal and alert instead of silently ignoring

Example fix

// before
if err := h.Kill(); err != nil { return err }
// after
if err := h.Kill(); err != nil {
    if errors.Is(err, windows.ERROR_ACCESS_DENIED) {
        return fmt.Errorf("kill %d: need elevated rights: %w", pid, err)
    }
    return err
}
Defensive patterns

Strategy: try-catch

Validate before calling

// Confirm we can open with terminate rights before keeping a long-lived handle
h, err := procid.Open(pid, tok)
if err != nil {
    return err // surfaces ERROR_ACCESS_DENIED early, before kill time
}

Type guard

func canTerminate(pid int, tok procid.Token) bool {
    h, err := procid.Open(pid, tok)
    if err != nil { return false }
    _ = h.Close()
    return true
}

Try / catch

if err := h.Kill(); err != nil {
    if errors.Is(err, windows.ERROR_ACCESS_DENIED) {
        return fmt.Errorf("kill: insufficient privileges: %w", err)
    }
    return err // note: already-exited targets return nil from Signal
}

Prevention

When it happens

Trigger: Calling Handle.Signal/Kill when TerminateProcess fails with an error other than "process exited between the liveness check and the kill" — typically ERROR_ACCESS_DENIED on a protected process.

Common situations: Killing a process running as another user or elevated while the caller is not; target protected by antivirus/EDR; killing a system-critical process.

Related errors


AI-assisted analysis of gastownhall/beads@71377f2769 (2026-08-30). Data as JSON: /api/errors/8dcbed4e2b430735. Report an issue: GitHub.