gastownhall/beads · error

read command line for pid %d: %w

Error message

read command line for pid %d: %w

What it means

On macOS, unverifiedProcess.commandLineContains inspects a PID's arguments by running `ps -p <pid> -o args=`. If ps fails and a follow-up kill(pid,0) shows the process still exists (not ESRCH), the ps failure is wrapped in this error. This keeps the force-stop path safe: the proxy cannot verify what the PID is running, so it refuses to act rather than guessing.

Source

Thrown at internal/storage/dbproxy/proxy/unverified_process_darwin.go:44

	if killErr := syscall.Kill(pid, 0); errors.Is(killErr, unix.ESRCH) {
		return nil, true, nil
	}
	return &unverifiedProcess{pid: pid}, false, nil
}

func (p *unverifiedProcess) executableBasename() (basename string, gone bool, err error) {
	return processExecutableBasename(p.pid)
}

// commandLineContains reports whether the process command line contains
// needle, read best-effort through ps.
func (p *unverifiedProcess) commandLineContains(needle string) (matched bool, gone bool, err error) {
	output, commandErr := exec.Command("ps", "-p", strconv.Itoa(p.pid), "-o", "args=").Output()
	if commandErr != nil {
		if killErr := syscall.Kill(p.pid, 0); errors.Is(killErr, unix.ESRCH) {
			return false, true, nil
		}
		return false, false, fmt.Errorf("read command line for pid %d: %w", p.pid, commandErr)
	}
	return strings.Contains(strings.TrimSpace(string(output)), needle), false, nil
}

// kill sends SIGKILL. gone reports a target that had already exited.
func (p *unverifiedProcess) kill() (gone bool, err error) {
	if killErr := syscall.Kill(p.pid, syscall.SIGKILL); killErr != nil {
		if errors.Is(killErr, unix.ESRCH) {
			return true, nil
		}
		return false, fmt.Errorf("signal pid %d: %w", p.pid, killErr)
	}
	return false, nil
}

func (p *unverifiedProcess) exited() (bool, error) {
	_, gone, err := processExecutableBasename(p.pid)
	if err != nil {

View on GitHub (pinned to 71377f2769)

Solutions

  1. Verify /bin/ps works standalone: `ps -p <pid> -o args=` — fix PATH or environment if it fails there.
  2. Re-run the command; transient fork/exec failures often clear on retry.
  3. Check whether the process is a zombie (state Z in `ps aux | grep <pid>`); reap the parent or wait for it to exit.
  4. Manually kill the PID after verifying its identity, then remove the stale pidfile.

Example fix

// before: PATH stripped in daemon env, ps not found
PATH=/usr/bin:/bin:/usr/sbin:/sbin bd ...
// after: ensure standard paths so exec ps succeeds
export PATH="/usr/bin:/bin:/usr/sbin:/sbin:$PATH"
Defensive patterns

Strategy: try-catch

Try / catch

matched, gone, err := proc.commandLineContains(needle)
if err != nil {
    if _, statErr := exec.LookPath("ps"); statErr != nil {
        // environment problem: fix PATH or inspect the PID manually
    }
    return fmt.Errorf("cannot verify pid identity, refusing to kill: %w", err)
}

Prevention

When it happens

Trigger: commandLineContains runs exec.Command("ps", "-p", pid, "-o", "args=") and it errors while syscall.Kill(pid, 0) does NOT return ESRCH — i.e. ps failed but the process appears alive.

Common situations: Sandboxed/restricted macOS environments where spawning ps fails; ps binary missing from PATH; transient resource exhaustion preventing fork/exec; zombie or system-protected processes ps cannot report on.

Related errors


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