gastownhall/beads · warning
procid: process %d no longer matches token
Error message
procid: process %d no longer matches token
What it means
This error comes from Handle.Signal on macOS. Before delivering a signal, the handle re-verifies that the target PID's process start time still matches the token captured earlier. If the process has exited and its PID was recycled by an unrelated process, the library refuses to signal, because the signal would hit a different process than the one the handle refers to.
Source
Thrown at internal/procid/procid_darwin.go:61
func Open(pid int, tok Token) (*Handle, error) {
match, err := Verify(pid, tok)
if err != nil {
return nil, err
}
if !match {
return nil, fmt.Errorf("procid: process %d does not match token", pid)
}
return &Handle{pid: pid, token: tok}, nil
}
func (h *Handle) Signal(sig os.Signal) error {
match, err := Verify(h.pid, h.token)
if err != nil {
return err
}
if !match {
return fmt.Errorf("procid: process %d no longer matches token", h.pid)
}
unixSig, ok := sig.(syscall.Signal)
if !ok {
return fmt.Errorf("procid: unsupported signal %v", sig)
}
if err := syscall.Kill(h.pid, unixSig); err != nil {
if isFatalSignal(unixSig) && errors.Is(err, unix.ESRCH) {
return nil
}
return fmt.Errorf("procid: signal %d: %w", h.pid, err)
}
if isFatalSignal(unixSig) {
return h.confirmFatalSignal()
}
match, err = Verify(h.pid, h.token)
if err != nil {
return err
}View on GitHub (pinned to 71377f2769)
Solutions
- Treat this as the target process being gone: check procid.IsProcessGone / re-Capture the PID and reconcile your records.
- Re-capture a fresh token with procid.Capture on the new process if you genuinely intend to control the replacement.
- Avoid holding procid Handles across process restarts; open a new Handle with procid.Open right before use.
- If this happens spuriously in tests, ensure the child process is still alive at signal time (check cmd.Process state).
Example fix
// before
handle, _ := procid.Open(pid, tok) // captured long ago
_ = handle.Kill() // PID may now belong to another process
// after
match, err := procid.Verify(pid, tok)
if err != nil || !match {
// re-capture or re-spawn the target
tok, err = procid.Capture(pid)
}
handle, _ := procid.Open(pid, tok)
_ = handle.Kill() Defensive patterns
Strategy: try-catch
Validate before calling
match, err := procid.Verify(pid, tok)
if err != nil || !match {
// process gone or replaced; do not signal
return
} Type guard
func stillValid(h *procid.Handle, pid int, tok procid.Token) bool {
m, err := procid.Verify(pid, tok)
return err == nil && m
} Try / catch
if err := h.Kill(); err != nil {
if strings.Contains(err.Error(), "no longer matches token") || procid.IsProcessGone(err) {
// target exited or PID recycled: cleanup, re-capture, or re-spawn
return
}
return fmt.Errorf("kill %d: %w", pid, err)
} Prevention
- Open handles with procid.Open immediately before signaling; do not cache them across restarts
- Call procid.Verify before signaling a long-lived recorded PID
- Use procid.IsProcessGone to distinguish exit from PID reuse
- Re-capture tokens after process restarts instead of reusing old ones
When it happens
Trigger: Calling Handle.Signal (directly or via Handle.Kill) on a darwin Handle after the originally captured process has exited and another process now occupies the same PID number.
Common situations: Killing a stale recorded PID after the worker crashed and was replaced; long-lived supervisor reusing cached handles across restarts; PID reuse in constrained PID spaces on macOS.
Related errors
- ErrUnverifiableProcess
- procid: process %d does not match token
- procid: process no longer matches token
- proxy.ForceStopUnverified: open pid %d: %w
- empty executable basename for pid %d
AI-assisted analysis of gastownhall/beads@71377f2769 (2026-08-30).
Data as JSON: /api/errors/6e30b53f83afbc5e.
Report an issue: GitHub.