gastownhall/beads · error
read cmdline for pid %d: %w
Error message
read cmdline for pid %d: %w
What it means
unverifiedProcess.commandLineContains on Linux reads /proc/<pid>/cmdline to check what the PID is running. Missing cmdline (ENoENT/ESRCH) or empty data (zombie) is treated as 'gone'; any other read error is wrapped here. This gate exists so the proxy never kills a PID it could not identify.
Source
Thrown at internal/storage/dbproxy/proxy/unverified_process_linux.go:56
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 {
// 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)View on GitHub (pinned to 71377f2769)
Solutions
- Ensure /proc is mounted inside the container/chroot (mount -t proc proc /proc).
- Fix /proc permissions (avoid hidepid=2, or run the tool as the process owner/root).
- Adjust SELinux/AppArmor policy to allow reading /proc/<pid>/cmdline.
- Kill the PID manually after verifying identity another way, then remove the stale pidfile.
Example fix
// before: /proc missing in chroot mount -t proc proc /proc // after: cmdline readable ls /proc/<pid>/cmdline && bd ...
Defensive patterns
Strategy: try-catch
Validate before calling
if _, err := os.Stat("/proc/self/cmdline"); err != nil {
// /proc unavailable: force-stop via /proc cannot work; use manual cleanup
} Try / catch
matched, gone, err := proc.commandLineContains(needle)
if err != nil {
var pathErr *fs.PathError
if errors.As(err, &pathErr) && errors.Is(pathErr.Err, fs.ErrPermission) {
// hidepid/LSM issue: rerun as process owner or fix mount options
}
return err
} Prevention
- Mount /proc in containers/chroots where bd runs.
- Avoid hidepid mount options on /proc for multi-user bd usage.
- Configure SELinux/AppArmor to allow reading /proc/<pid>/cmdline for the bd user.
- Do not run bd inside PID namespaces that hide the daemon's PID.
When it happens
Trigger: os.ReadFile("/proc/<pid>/cmdline") fails with an error other than ENOENT/ESRCH — e.g. EACCES on the /proc entry, or the /proc filesystem is not mounted (containers/chroots).
Common situations: Running inside a container without /proc mounted; hidepid mount option on /proc making other users' process entries inaccessible; hardened LSM (SELinux/AppArmor) denying reads; PID namespaces mismatch between host and container.
Related errors
- open process %d: %w
- dolt path is not executable
- procid: malformed proc stat: missing comm terminator
- procid: malformed proc stat: missing starttime
- failed to create backup directory: %w
AI-assisted analysis of gastownhall/beads@71377f2769 (2026-08-30).
Data as JSON: /api/errors/998173654b98079c.
Report an issue: GitHub.