gastownhall/beads · error
pidfd open %d: %w
Error message
pidfd open %d: %w
What it means
On Linux, openUnverifiedProcess first tries pidfd_open to get a stable handle on the PID (preventing PID-recycling races). If pidfd_open fails with anything other than ESRCH (gone) or ENOSYS (kernel too old, fallback allowed), the error is wrapped here and the operation aborts. The proxy needs either a pidfd or the ENOSYS fallback to inspect the process safely.
Source
Thrown at internal/storage/dbproxy/proxy/unverified_process_linux.go:40
type unverifiedProcess struct {
pid int
pidfd int // -1 when the kernel has no pidfd support
}
// openUnverifiedProcess opens a stable handle for pid. gone reports a PID
// that no longer exists.
func openUnverifiedProcess(pid int) (proc *unverifiedProcess, gone bool, err error) {
fd, err := unix.PidfdOpen(pid, 0)
if err == nil {
return &unverifiedProcess{pid: pid, pidfd: fd}, false, nil
}
if errors.Is(err, unix.ESRCH) {
return nil, true, nil
}
if errors.Is(err, unix.ENOSYS) {
return &unverifiedProcess{pid: pid, pidfd: -1}, false, nil
}
return nil, false, fmt.Errorf("pidfd open %d: %w", pid, err)
}
func (p *unverifiedProcess) executableBasename() (basename string, gone bool, err error) {
return processExecutableBasename(p.pid)
}
// commandLineContains reports whether the process command line contains
// needle. The managed proxy child is spawned as "db-proxy-child --root
// <rootDir>", so a workspace's own processes always match their root path.
func (p *unverifiedProcess) commandLineContains(needle string) (matched bool, gone bool, err error) {
data, err := os.ReadFile("/proc/" + strconv.Itoa(p.pid) + "/cmdline")
if err != nil {
if errors.Is(err, fs.ErrNotExist) || errors.Is(err, unix.ESRCH) {
return false, true, nil
}
return false, false, fmt.Errorf("read cmdline for pid %d: %w", p.pid, err)
}
if len(data) == 0 {View on GitHub (pinned to 71377f2769)
Solutions
- Raise the process fd limit (ulimit -n) if EMFILE/ENFILE is the cause.
- Upgrade the kernel to >=5.3 so pidfd_open is supported normally.
- Adjust seccomp/container profile to allow pidfd_open (or return ENOSYS so the fallback path is used).
- Retry the stop operation — transient fd exhaustion often clears.
Example fix
// before: seccomp blocks pidfd_open with EPERM
// docker run --security-opt seccomp=default.json ...
// after: allow or downgrade unknown syscalls to ENOSYS
// seccomp profile: {"names":["pidfd_open"],"action":"SCMP_ACT_ERRNO","errnoRet":38} Defensive patterns
Strategy: retry
Validate before calling
// probe pidfd support before relying on the fast path
fd, _, errno := unix.Syscall(unix.SYS_PIDFD_OPEN, uintptr(pid), 0, 0)
supported := errno != unix.ENOSYS && errno != unix.EPERM
if !supported { /* expect/require the ENOSYS fallback or skip force-stop */ } Try / catch
proc, gone, err := openUnverifiedProcess(pid)
if err != nil {
if errors.Is(err, unix.EMFILE) || errors.Is(err, unix.ENFILE) {
// transient fd exhaustion: wait and retry once
}
return err
} Prevention
- Run on kernel >= 5.3 where pidfd_open is standard.
- Raise the fd limit (ulimit -n) for long-running daemons.
- Use seccomp profiles that return ENOSYS (not EPERM) for unknown syscalls so the fallback path activates.
- Close leaked pidfds/fds in tooling that holds process handles.
When it happens
Trigger: openUnverifiedProcess calls pidfd_open(pid) on Linux and the syscall returns an unexpected errno — e.g. EMFILE/ENFILE (fd/table exhaustion), EINVAL (bad flags/kernel), EPERM in hardened environments.
Common situations: Very old kernels (<5.3) lacking pidfd_open combined with seccomp returning something other than ENOSYS; fd limits exhausted after long daemon uptime; restricted containers/seccomp profiles that return EPERM for unknown syscalls.
Related errors
- procid: pidfd open %d: %w
- pidfd signal %d: %w
- procid: unsupported signal %v
- procid: pidfd signal %d: %w
- read cmdline for pid %d: %w
AI-assisted analysis of gastownhall/beads@71377f2769 (2026-08-30).
Data as JSON: /api/errors/800329740f06eb9e.
Report an issue: GitHub.