{"record":{"id":"eb5ccb5d68009116","repo":"gastownhall/beads","slug":"procid-process-d-does-not-match-token-eb5ccb","errorCode":null,"errorMessage":"procid: process %d does not match token","messagePattern":"procid: process (.+?) does not match token","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"warning","filePath":"internal/procid/procid_linux.go","lineNumber":81,"sourceCode":"// The pidfd is opened before the token check: a pidfd pins the PID number\n// against reuse, so verifying afterwards proves the fd refers to the process\n// the token describes. Verifying first would leave a window where the\n// verified process exits, the PID is recycled, and the pidfd targets the\n// unrelated replacement.\nfunc Open(pid int, tok Token) (*Handle, error) {\n\tfd, err := pidfdOpen(pid, 0)\n\tif err != nil {\n\t\tif !errors.Is(err, unix.ENOSYS) {\n\t\t\treturn nil, fmt.Errorf(\"procid: pidfd open %d: %w\", pid, err)\n\t\t}\n\t\t// Kernel without pidfds: fall back to verify-then-signal, which\n\t\t// retains the documented small PID-reuse race.\n\t\tmatch, verifyErr := Verify(pid, tok)\n\t\tif verifyErr != nil {\n\t\t\treturn nil, verifyErr\n\t\t}\n\t\tif !match {\n\t\t\treturn nil, fmt.Errorf(\"procid: process %d does not match token\", pid)\n\t\t}\n\t\treturn &Handle{pid: pid, token: tok, pidfd: -1}, nil\n\t}\n\tmatch, verifyErr := Verify(pid, tok)\n\tif verifyErr != nil {\n\t\t_ = unix.Close(fd)\n\t\treturn nil, verifyErr\n\t}\n\tif !match {\n\t\t_ = unix.Close(fd)\n\t\treturn nil, fmt.Errorf(\"procid: process %d does not match token\", pid)\n\t}\n\treturn &Handle{pid: pid, token: tok, pidfd: fd}, nil\n}\n\n// Signal sends sig to the verified process.\nfunc (h *Handle) Signal(sig os.Signal) error {\n\tif h.pidfd >= 0 {","sourceCodeStart":63,"sourceCodeEnd":99,"githubUrl":"https://github.com/gastownhall/beads/blob/71377f276968b452ee607177637970a4ff888584/internal/procid/procid_linux.go#L63-L99","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","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."],"exampleFix":"// before\nh, err := procid.Open(oldPid, tok) // process does not match token\n// after\nmatch, verr := procid.Verify(oldPid, tok)\nif verr != nil || !match {\n    // mark record as dead / re-capture the replacement\n    tok, _ = procid.Capture(oldPid)\n}\nh, err := procid.Open(oldPid, tok)","handlingStrategy":"validation","validationCode":"match, err := procid.Verify(pid, tok)\nif err != nil || !match {\n    // recorded PID is stale or recycled: skip or re-capture\n    return\n}","typeGuard":"func isStaleRecord(pid int, tok procid.Token) bool {\n    m, err := procid.Verify(pid, tok)\n    return err != nil || !m\n}","tryCatchPattern":"h, err := procid.Open(pid, tok)\nif err != nil {\n    if strings.Contains(err.Error(), \"does not match token\") && procid.IsProcessGone(err) {\n        // plain exit: mark record dead\n    } else if strings.Contains(err.Error(), \"does not match token\") {\n        // PID recycled: invalidate old token, re-capture\n    }\n    return err\n}","preventionTips":["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"],"tags":["linux","pid-reuse","fallback","process-lifecycle"],"backgroundTag":"pid-reuse-mismatch","analyzedSha":"71377f276968b452ee607177637970a4ff888584","analyzedAt":"2026-08-30T18:55:39.744Z","schemaVersion":2},"datasetVersion":"2026-08-30T23:17:21.991Z"}