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 = -1View on GitHub (pinned to 71377f2769)
Solutions
- 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.
- Run the stop command as the same user (or with sudo, after identity verification).
- Upgrade the kernel to >=5.3 to get the safer pidfd path.
- 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
- Prefer kernels >=5.3 so the pidfd (recycle-safe) path is used instead of plain kill.
- Check /proc/<pid>/cmdline ownership/identity before any manual kill on EPERM.
- Run stop commands as the daemon's owning user.
- Reap stale pidfiles promptly after crashes to reduce PID-recycling windows.
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
- pidfd signal %d: %w
- signal pid %d: %w
- procid: unsupported signal %v
- procid: pidfd signal %d: %w
- procid: process %d still matches token after fatal signal an
AI-assisted analysis of gastownhall/beads@71377f2769 (2026-08-30).
Data as JSON: /api/errors/5e6e73ebfa183427.
Report an issue: GitHub.