{"record":{"id":"6e30b53f83afbc5e","repo":"gastownhall/beads","slug":"procid-process-d-no-longer-matches-token","errorCode":null,"errorMessage":"procid: process %d no longer matches token","messagePattern":"procid: process (.+?) no longer matches token","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"warning","filePath":"internal/procid/procid_darwin.go","lineNumber":61,"sourceCode":"\nfunc Open(pid int, tok Token) (*Handle, error) {\n\tmatch, err := Verify(pid, tok)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tif !match {\n\t\treturn nil, fmt.Errorf(\"procid: process %d does not match token\", pid)\n\t}\n\treturn &Handle{pid: pid, token: tok}, nil\n}\n\nfunc (h *Handle) Signal(sig os.Signal) error {\n\tmatch, err := Verify(h.pid, h.token)\n\tif err != nil {\n\t\treturn err\n\t}\n\tif !match {\n\t\treturn fmt.Errorf(\"procid: process %d no longer matches token\", h.pid)\n\t}\n\tunixSig, ok := sig.(syscall.Signal)\n\tif !ok {\n\t\treturn fmt.Errorf(\"procid: unsupported signal %v\", sig)\n\t}\n\tif err := syscall.Kill(h.pid, unixSig); err != nil {\n\t\tif isFatalSignal(unixSig) && errors.Is(err, unix.ESRCH) {\n\t\t\treturn nil\n\t\t}\n\t\treturn fmt.Errorf(\"procid: signal %d: %w\", h.pid, err)\n\t}\n\tif isFatalSignal(unixSig) {\n\t\treturn h.confirmFatalSignal()\n\t}\n\tmatch, err = Verify(h.pid, h.token)\n\tif err != nil {\n\t\treturn err\n\t}","sourceCodeStart":43,"sourceCodeEnd":79,"githubUrl":"https://github.com/gastownhall/beads/blob/71377f276968b452ee607177637970a4ff888584/internal/procid/procid_darwin.go#L43-L79","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","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)."],"exampleFix":"// before\nhandle, _ := procid.Open(pid, tok) // captured long ago\n_ = handle.Kill() // PID may now belong to another process\n// after\nmatch, err := procid.Verify(pid, tok)\nif err != nil || !match {\n    // re-capture or re-spawn the target\n    tok, err = procid.Capture(pid)\n}\nhandle, _ := procid.Open(pid, tok)\n_ = handle.Kill()","handlingStrategy":"try-catch","validationCode":"match, err := procid.Verify(pid, tok)\nif err != nil || !match {\n    // process gone or replaced; do not signal\n    return\n}","typeGuard":"func stillValid(h *procid.Handle, pid int, tok procid.Token) bool {\n    m, err := procid.Verify(pid, tok)\n    return err == nil && m\n}","tryCatchPattern":"if err := h.Kill(); err != nil {\n    if strings.Contains(err.Error(), \"no longer matches token\") || procid.IsProcessGone(err) {\n        // target exited or PID recycled: cleanup, re-capture, or re-spawn\n        return\n    }\n    return fmt.Errorf(\"kill %d: %w\", pid, err)\n}","preventionTips":["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"],"tags":["process","pid-reuse","darwin","safety"],"backgroundTag":"pid-reuse-mismatch","analyzedSha":"71377f276968b452ee607177637970a4ff888584","analyzedAt":"2026-08-30T18:55:39.744Z","schemaVersion":2},"datasetVersion":"2026-08-30T23:17:21.991Z"}