gastownhall/beads · error
procid: pidfd open %d: %w
Error message
procid: pidfd open %d: %w
What it means
On Linux, Open first calls pidfd_open to pin the PID against reuse. If that syscall fails with anything other than ENOSYS (kernel too old), the error is wrapped and returned — the handle could not be created safely. Typical errnos are ESRCH (process already gone), EINVAL, EMFILE/ENFILE (fd exhaustion), or EPERM in restricted environments (seccomp/containers blocking pidfd_open).
Source
Thrown at internal/procid/procid_linux.go:72
return false, nil
}
return false, err
}
return current == tok, nil
}
// Open verifies pid's token and opens a handle suitable for safe signaling.
//
// 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 {View on GitHub (pinned to 71377f2769)
Solutions
- Check errors.Is(err, unix.ESRCH): the process is already gone; reconcile and skip.
- Check errors.Is(err, unix.EMFILE) || errors.Is(err, unix.ENFILE): close leaked fds / raise RLIMIT_NOFILE, then retry.
- If running in a container, update the seccomp/runtime profile to allow pidfd_open, or upgrade container runtime.
- Upgrade the kernel to >=5.3 so pidfd is available (older kernels fall back via ENOSYS path, which is different).
Example fix
// before
h, err := procid.Open(pid, tok) // procid: pidfd open 4242: too many open files
// after
if err != nil && (errors.Is(err, unix.EMFILE) || errors.Is(err, unix.ENFILE)) {
runtime.GC() // release fds held by finalizers, close idle conns
h, err = procid.Open(pid, tok)
} Defensive patterns
Strategy: try-catch
Validate before calling
// Check kernel support and target liveness before Open
if _, err := os.Stat("/proc/sys/kernel/random/boot_id"); err != nil { /* env broken */ }
if err := syscall.Kill(pid, 0); err != nil {
if errors.Is(err, unix.ESRCH) { return /* already gone */ }
} Type guard
func isPidfdOpenFailure(err error) bool {
return strings.HasPrefix(err.Error(), "procid: pidfd open ")
} Try / catch
h, err := procid.Open(pid, tok)
if err != nil {
switch {
case errors.Is(err, unix.ESRCH):
return // process gone
case errors.Is(err, unix.EMFILE), errors.Is(err, unix.ENFILE):
// free fds / raise RLIMIT_NOFILE and retry once
case errors.Is(err, unix.EPERM):
// seccomp blocked pidfd_open: fix container profile or accept fallback
}
return err
} Prevention
- Raise RLIMIT_NOFILE in services that open many handles
- Ensure container seccomp profiles allow pidfd_open (kernel >=5.3, modern runtimes)
- Close handles (h.Close()) promptly to avoid fd leaks
- Probe with syscall.Kill(pid, 0) before opening a stale PID
When it happens
Trigger: procid.Open on Linux where unix.PidfdOpen fails with ESRCH (target exited before Open), EMFILE/ENFILE (process/file descriptor limit reached), EPERM (seccomp filter blocks pidfd_open), EINVAL.
Common situations: Killing an already-dead recorded PID; containers with restrictive seccomp profiles (older Docker default) that deny pidfd syscalls; hitting RLIMIT_NOFILE in servers with many open fds; very old kernels (<5.3) return ENOSYS and fall back instead — those do NOT produce this error.
Related errors
- pidfd open %d: %w
- procid: unsupported signal %v
- procid: pidfd signal %d: %w
- pidfd signal %d: %w
- procid: malformed proc stat: missing comm terminator
AI-assisted analysis of gastownhall/beads@71377f2769 (2026-08-30).
Data as JSON: /api/errors/4a430ae058a30423.
Report an issue: GitHub.