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
- Check errors.Is(err, windows.ERROR_ACCESS_DENIED); run elevated or take ownership of the target process lifecycle
- Confirm the process wasn't protected by security software and adjust policy or target the right process
- Re-open with Open and retry once, in case the handle became stale
- 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
- Open the handle (with PROCESS_TERMINATE) early to fail fast on privilege problems
- Run the killing component under an account that owns or can control the target
- Treat Signal returning nil as 'goal met' — it already handles exited targets
- Keep antivirus/EDR exclusions in mind when killing child processes
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
- procid: get process exit code: %w
- procid: get process times: %w
- failed to create .beads directory: %v Windows Controlled Fo
- finding process %d: %w
- sending SIGTERM to PID %d: %w
AI-assisted analysis of gastownhall/beads@71377f2769 (2026-08-30).
Data as JSON: /api/errors/8dcbed4e2b430735.
Report an issue: GitHub.