gastownhall/beads · error
procid: process %d does not match token
Error message
procid: process %d does not match token
What it means
procid.Open successfully opened the process, but tokenForProcess returned a creation-time token different from the supplied token, so Open refuses to return a handle. This is the PID-reuse guard: the PID now refers to a different process instance than the one the token was minted for.
Source
Thrown at internal/procid/procid_windows.go:69
return false, nil
}
return false, err
}
return current == tok, nil
}
func Open(pid int, tok Token) (*Handle, error) {
process, err := openProcess(pid, windows.PROCESS_QUERY_LIMITED_INFORMATION|windows.PROCESS_TERMINATE)
if err != nil {
return nil, fmt.Errorf("procid: open process %d: %w", pid, err)
}
current, err := tokenForProcess(process)
if err != nil || current != tok {
_ = windows.CloseHandle(process)
if err != nil {
return nil, err
}
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)View on GitHub (pinned to 71377f2769)
Solutions
- Treat this as PID reuse: re-capture a fresh token with Capture(pid) before opening
- Verify with Verify(pid, tok) before Open to detect mismatch without error noise
- Persist the process creation time/token alongside the PID and refresh it whenever the target restarts
- Never share tokens across machines or OS platforms (token format is windows-v1 creation time)
Example fix
// before
h, err := procid.Open(pid, oldTok)
// after
if ok, _ := procid.Verify(pid, oldTok); !ok {
newTok, terr := procid.Capture(pid)
if terr != nil { return terr }
oldTok = newTok
}
h, err := procid.Open(pid, oldTok) Defensive patterns
Strategy: validation
Validate before calling
ok, err := procid.Verify(pid, tok)
if err != nil {
return err
}
if !ok {
tok, err = procid.Capture(pid)
if err != nil {
return err
}
} Type guard
func tokenMatches(pid int, tok procid.Token) bool {
ok, err := procid.Verify(pid, tok)
return err == nil && ok
} Try / catch
h, err := procid.Open(pid, tok)
if err != nil {
if strings.Contains(err.Error(), "does not match token") || tokenIsStale {
newTok, cerr := procid.Capture(pid)
if cerr != nil { return cerr }
h, err = procid.Open(pid, newTok)
}
if err != nil { return err }
} Prevention
- Re-capture tokens after any target process restart
- Do not reuse tokens captured on a different machine or OS
- Treat 'does not match token' as PID reuse, never retry with the same token
- Keep token lifetime tied to the target process lifecycle, not to config files
When it happens
Trigger: Calling Open(pid, tok) after the original process exited and Windows recycled the PID for a new process; passing a token captured from a different PID.
Common situations: Long-running daemons holding tokens across restarts where the target was restarted and got the same PID; tokens persisted to disk and reused much later; copying a token between hosts or records.
Related errors
- procid: process no longer matches token
- finding process %d: %w
- finding process %d: %w
- procid: process %d no longer matches token
- procid: terminate process: %w
AI-assisted analysis of gastownhall/beads@71377f2769 (2026-08-30).
Data as JSON: /api/errors/4b3716525cf75a2d.
Report an issue: GitHub.