gastownhall/beads · error

signal pid %d: %w

Error message

signal pid %d: %w

What it means

Fallback path of unverifiedProcess.kill on Linux (used when pidfd is unavailable, e.g. ENOSYS on old kernels): plain syscall.Kill(pid, SIGKILL). ESRCH is success (already gone); any other error is wrapped here. Without a pidfd, the PID is not protected from recycling, so this error means the proxy must stop rather than risk signaling an unrelated process.

Source

Thrown at internal/storage/dbproxy/proxy/unverified_process_linux.go:82

}

// 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
	}
	return gone, nil
}

func (p *unverifiedProcess) close() {
	if p.pidfd >= 0 {
		_ = unix.Close(p.pidfd)
		p.pidfd = -1

View on GitHub (pinned to 71377f2769)

Solutions

  1. Verify the PID's identity with `cat /proc/<pid>/cmdline`; if recycled to another user's process, do not kill — just remove the stale pidfile.
  2. Run the stop command as the same user (or with sudo, after identity verification).
  3. Upgrade the kernel to >=5.3 to get the safer pidfd path.
  4. Check LSM/seccomp policies blocking signals and relax them for this operation.

Example fix

// before: recycled PID owned by another user
cat /proc/<pid>/cmdline  # verify owner/identity first
// after: only remove stale record, never kill foreign PID
rm .beads/bd.pid && bd ...
Defensive patterns

Strategy: type-guard

Type guard

func isSignalErr(err error) (errno syscall.Errno, ok bool) {
    if errors.As(err, &errno) && (errno == syscall.EPERM || errno == syscall.EACCES) {
        return errno, true
    }
    return 0, false
}

Try / catch

gone, err := proc.kill() // pidfd == -1 fallback
if errno, ok := isSignalErr(err); ok && errno == syscall.EPERM {
    // PID likely recycled to another user: inspect /proc/<pid>/cmdline, never blind-kill
    return err
}

Prevention

When it happens

Trigger: kill() with pidfd == -1 calls syscall.Kill(pid, SIGKILL) and gets a non-ESRCH error — typically EPERM (process owned by another UID, e.g. recycled PID) or EPERM from LSM policy.

Common situations: Old kernels without pidfd support combined with PID recycling to another user's process; restricted containers where signaling is blocked; running bd as a different user than the daemon.

Related errors


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