gastownhall/beads · warning
procid: process %d does not match token
Error message
procid: process %d does not match token
What it means
On kernels without pidfd support (pidfd_open returns ENOSYS), Open falls back to verifying the token before creating the handle. If the verification shows the PID's process start time does not match the captured token — the process exited, or the PID was recycled by a different process — Open fails with this error.
Source
Thrown at internal/procid/procid_linux.go:81
// The pidfd is opened before the token check: a pidfd pins the PID number
// against reuse, so verifying afterwards proves the fd refers to the process
// the token describes. Verifying first would leave a window where the
// verified process exits, the PID is recycled, and the pidfd targets the
// unrelated replacement.
func Open(pid int, tok Token) (*Handle, error) {
fd, err := pidfdOpen(pid, 0)
if err != nil {
if !errors.Is(err, unix.ENOSYS) {
return nil, fmt.Errorf("procid: pidfd open %d: %w", pid, err)
}
// Kernel without pidfds: fall back to verify-then-signal, which
// retains the documented small PID-reuse race.
match, verifyErr := Verify(pid, tok)
if verifyErr != nil {
return nil, verifyErr
}
if !match {
return nil, fmt.Errorf("procid: process %d does not match token", pid)
}
return &Handle{pid: pid, token: tok, pidfd: -1}, nil
}
match, verifyErr := Verify(pid, tok)
if verifyErr != nil {
_ = unix.Close(fd)
return nil, verifyErr
}
if !match {
_ = unix.Close(fd)
return nil, fmt.Errorf("procid: process %d does not match token", pid)
}
return &Handle{pid: pid, token: tok, pidfd: fd}, nil
}
// Signal sends sig to the verified process.
func (h *Handle) Signal(sig os.Signal) error {
if h.pidfd >= 0 {View on GitHub (pinned to 71377f2769)
Solutions
- Call procid.Verify(pid, tok) first to confirm the process identity before expecting Open to succeed; if it no longer matches, treat the recorded process as gone.
- Re-capture the token with procid.Capture if you intend to control the new process occupying the PID.
- Upgrade the kernel to >=5.3 to get the safer pidfd-based path.
- Check with procid.IsProcessGone on the underlying error to distinguish exit from token mismatch.
Example fix
// before
h, err := procid.Open(oldPid, tok) // process does not match token
// after
match, verr := procid.Verify(oldPid, tok)
if verr != nil || !match {
// mark record as dead / re-capture the replacement
tok, _ = procid.Capture(oldPid)
}
h, err := procid.Open(oldPid, tok) Defensive patterns
Strategy: validation
Validate before calling
match, err := procid.Verify(pid, tok)
if err != nil || !match {
// recorded PID is stale or recycled: skip or re-capture
return
} Type guard
func isStaleRecord(pid int, tok procid.Token) bool {
m, err := procid.Verify(pid, tok)
return err != nil || !m
} Try / catch
h, err := procid.Open(pid, tok)
if err != nil {
if strings.Contains(err.Error(), "does not match token") && procid.IsProcessGone(err) {
// plain exit: mark record dead
} else if strings.Contains(err.Error(), "does not match token") {
// PID recycled: invalidate old token, re-capture
}
return err
} Prevention
- Verify before Open when running on old kernels (<5.3)
- Invalidate all stored tokens after a reboot (tokens embed boot_id)
- Capture tokens immediately before opening handles, never long in advance
- Keep a process registry that removes entries on exit
When it happens
Trigger: procid.Open on a pre-5.3 Linux kernel (or ENOSYS pidfd path) where procid.Verify(pid, tok) returns match=false: target already exited, or PID reused by an unrelated process since Capture.
Common situations: Old kernels (CentOS 7 / Ubuntu 18.04 era, kernels <5.3); acting on stale PIDs recorded in a database after a restart; test environments with recycled PIDs.
Related errors
- procid: malformed proc stat: missing comm terminator
- procid: malformed proc stat: missing starttime
- database read failed: %w; JSONL fallback read failed: %v
- procid: process %d no longer matches token
- procid: pidfd open %d: %w
AI-assisted analysis of gastownhall/beads@71377f2769 (2026-08-30).
Data as JSON: /api/errors/eb5ccb5d68009116.
Report an issue: GitHub.