gastownhall/beads · error
pidfd signal %d: %w
Error message
pidfd signal %d: %w
What it means
unverifiedProcess.kill on Linux prefers the pidfd-stable signal path: PidfdSendSignal(pidfd, SIGKILL) targets the exact process the handle refers to, immune to PID recycling. ESRCH means the process already exited (treated as success); any other error is wrapped in this error and the stop sequence aborts rather than risking an unverified kill.
Source
Thrown at internal/storage/dbproxy/proxy/unverified_process_linux.go:74
return false, false, fmt.Errorf("read cmdline for pid %d: %w", p.pid, err)
}
if len(data) == 0 {
// Zombies expose an empty cmdline; the process has effectively exited.
return false, true, nil
}
cmdline := strings.ReplaceAll(strings.TrimRight(string(data), "\x00"), "\x00", " ")
return strings.Contains(cmdline, needle), false, nil
}
// kill sends SIGKILL through the held handle. gone reports a target that had
// already exited.
func (p *unverifiedProcess) kill() (gone bool, err error) {
if p.pidfd >= 0 {
if err := unix.PidfdSendSignal(p.pidfd, unix.SIGKILL, nil, 0); err != nil {
if errors.Is(err, unix.ESRCH) {
return true, nil
}
return false, fmt.Errorf("pidfd signal %d: %w", p.pid, err)
}
return false, nil
}
if err := syscall.Kill(p.pid, syscall.SIGKILL); err != nil {
if errors.Is(err, unix.ESRCH) {
return true, nil
}
return false, fmt.Errorf("signal pid %d: %w", p.pid, err)
}
return false, nil
}
// exited reports whether the process is gone (or reduced to a zombie). While
// the pidfd is held the PID cannot be recycled, so a /proc probe is stable.
func (p *unverifiedProcess) exited() (bool, error) {
_, gone, err := processExecutableBasename(p.pid)
if err != nil {
return false, errView on GitHub (pinned to 71377f2769)
Solutions
- Re-run with elevated privileges (sudo) after manually verifying the PID identity via /proc/<pid>/cmdline.
- Check seccomp/container profile allows pidfd_send_signal.
- Close stale pidfds / retry the stop operation so a fresh pidfd is opened.
- As a last resort, kill by verified PID manually and delete the stale pidfile.
Example fix
// before: EPERM in container for cross-user pidfd signal sudo bd ... // after: stop succeeds with privileges # pidfile cleaned, daemon stopped
Defensive patterns
Strategy: try-catch
Try / catch
gone, err := proc.kill()
if err != nil {
if errors.Is(err, unix.EPERM) {
// target owned by another user/namespace: verify manually before escalating
}
return err
} Prevention
- Stop daemons from the same user account (and container/namespace) that started them.
- Allow pidfd_send_signal in seccomp/container profiles.
- Verify /proc/<pid>/cmdline identity before escalating privileges on EPERM.
- Keep kernel and libseccomp/golang.org/x/sys versions current.
When it happens
Trigger: kill() with pidfd >= 0 calls unix.PidfdSendSignal and gets an error other than ESRCH — e.g. EPERM (no permission for the target), EINVAL (bad flags), or the pidfd became invalid.
Common situations: Killing a process owned by another user (EPERM) after PID namespace changes in containers; seccomp blocking pidfd_send_signal; kernel quirks with pidfd on very new/patched kernels.
Related errors
- signal pid %d: %w
- procid: unsupported signal %v
- procid: pidfd signal %d: %w
- signal pid %d: %w
- pidfd open %d: %w
AI-assisted analysis of gastownhall/beads@71377f2769 (2026-08-30).
Data as JSON: /api/errors/4587ebe25330928e.
Report an issue: GitHub.