{"record":{"id":"fe5b7409b6ce53dc","repo":"gastownhall/beads","slug":"procid-signal-d-w","errorCode":null,"errorMessage":"procid: signal %d: %w","messagePattern":"procid: signal (.+?): %w","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"internal/procid/procid_darwin.go","lineNumber":71,"sourceCode":"}\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}\n\tif !match {\n\t\treturn fmt.Errorf(\"procid: process %d no longer matches token\", h.pid)\n\t}\n\treturn nil\n}\n\nfunc (h *Handle) confirmFatalSignal() error {\n\tdeadline := time.Now().Add(fallbackSignalConfirmTimeout)\n\tfor {\n\t\tmatch, err := Verify(h.pid, h.token)","sourceCodeStart":53,"sourceCodeEnd":89,"githubUrl":"https://github.com/gastownhall/beads/blob/71377f276968b452ee607177637970a4ff888584/internal/procid/procid_darwin.go#L53-L89","documentation":"This wraps the raw errno from syscall.Kill when sending a signal fails for a reason other than the special fatal-signal/ESRCH case. The process still matched the token before the kill, but the kernel rejected the signal delivery — commonly due to permissions (EPERM) or the process disappearing in a non-fatal-signal race (ESRCH).","triggerScenarios":"Handle.Signal on darwin where syscall.Kill returns an error: EPERM (no permission to signal the target, e.g. different uid, sandboxed), ESRCH while sending a non-fatal signal, EINVAL.","commonSituations":"Signaling a process owned by another user from a sandboxed/least-privileged app on macOS; container/CI runners restricting signals; target exiting between Verify and Kill when sending a non-fatal signal like SIGUSR1.","solutions":["Check errors.Is(err, unix.EPERM) and run the signaling code with sufficient privileges or as the same user as the target.","If ESRCH, treat the process as gone (for non-fatal signals the library does not swallow it) and reconcile state.","Verify the PID still matches your token and re-open the handle via procid.Open if your supervisor restarted the process."],"exampleFix":"// before\nerr := handle.Signal(syscall.SIGUSR1) // procid: signal 1234: operation not permitted\n// after\nif err := handle.Signal(syscall.SIGUSR1); err != nil {\n    if errors.Is(err, unix.EPERM) {\n        // escalate, or log and fall back to killing via parent/supervisor\n    } else if procid.IsProcessGone(err) {\n        // target already exited\n    }\n}","handlingStrategy":"try-catch","validationCode":"// Ensure same-user target before signaling\nif err := syscall.Kill(pid, 0); err != nil && errors.Is(err, unix.EPERM) {\n    // will not be able to signal; escalate or skip\n}","typeGuard":null,"tryCatchPattern":"if err := h.Signal(syscall.SIGTERM); err != nil {\n    var perr *os.SyscallError\n    if errors.As(err, &perr) && errors.Is(perr.Err, unix.EPERM) {\n        // permission denied: rerun with privileges or signal via parent\n    } else if procid.IsProcessGone(err) {\n        // target vanished; ignore\n    }\n    return err\n}","preventionTips":["Run the signaling process as the same user as the target or with sufficient privileges","Probe with signal 0 (syscall.Kill(pid, 0)) to check permission before real signals","Account for sandbox/macOS hardened-runtime restrictions in dev vs prod","Retry only on ESRCH-like transient errors, not EPERM"],"tags":["signal","permissions","darwin","eprocm"],"backgroundTag":"signal-permission-denied","analyzedSha":"71377f276968b452ee607177637970a4ff888584","analyzedAt":"2026-08-30T18:55:39.744Z","schemaVersion":2},"datasetVersion":"2026-08-30T23:17:21.991Z"}