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, err

View on GitHub (pinned to 71377f2769)

Solutions

  1. Re-run with elevated privileges (sudo) after manually verifying the PID identity via /proc/<pid>/cmdline.
  2. Check seccomp/container profile allows pidfd_send_signal.
  3. Close stale pidfds / retry the stop operation so a fresh pidfd is opened.
  4. 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

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


AI-assisted analysis of gastownhall/beads@71377f2769 (2026-08-30). Data as JSON: /api/errors/4587ebe25330928e. Report an issue: GitHub.