gastownhall/beads · error

proxy.ForceStopUnverified: signal pid %d: %w

Error message

proxy.ForceStopUnverified: signal pid %d: %w

What it means

All safety checks passed (executable is bd/dolt, command line scoped to this workspace) but the actual signal delivery to the PID failed. This wraps the underlying OS error from proc.kill() — typically EPERM (insufficient permission), ESRCH (process vanished between check and signal), or platform signal API failure. No signal was confirmed delivered.

Source

Thrown at internal/storage/dbproxy/proxy/force_stop.go:257

	if gone {
		report.ProcessWasGone = true
		return nil
	}
	if !scoped {
		return fmt.Errorf(
			"proxy.ForceStopUnverified: refusing to signal pid %d from %s: its command line does not reference workspace %s, so it may be an unrelated %s process; stop it manually if it is yours, then quarantine the record by renaming %s to %s.stale-<unix-timestamp> before retrying",
			pid,
			report.RecordPath,
			rootDir,
			executable,
			report.RecordPath,
			report.RecordPath,
		)
	}

	gone, err = proc.kill()
	if err != nil {
		return fmt.Errorf("proxy.ForceStopUnverified: signal pid %d: %w", pid, err)
	}
	if gone {
		report.ProcessWasGone = true
		return nil
	}
	report.SignalSent = true

	for {
		exited, err := proc.exited()
		if err != nil {
			return fmt.Errorf("proxy.ForceStopUnverified: confirm pid %d exit: %w", pid, err)
		}
		if exited {
			return nil
		}
		if time.Now().After(deadline) {
			return fmt.Errorf("proxy.ForceStopUnverified: timeout waiting for pid %d to exit", pid)
		}

View on GitHub (pinned to 71377f2769)

Solutions

  1. Re-run the force-stop; if the process died in the race window, the next attempt reports ProcessWasGone and succeeds
  2. Check process ownership (ps -o user= -p <pid>); run force-stop as the same user or with appropriate privileges (sudo / inside the container)
  3. If the process is gone but the record remains, quarantine it: rename <record> to <record>.stale-<unix-timestamp> and retry
  4. Inspect the wrapped cause (%w) for EPERM vs ESRCH to decide between a permissions fix and a stale-record cleanup

Example fix

// before
err := proxy.ForceStopUnverified(ctx, rootDir, "bd", deadline) // EPERM
// after: run with matching privileges or via sudo
// sudo -E bd daemon --force-stop
// or in CI: docker exec <container> bd daemon --force-stop
Defensive patterns

Strategy: retry

Validate before calling

// ensure same-user ownership of the target before signaling
pid := readPidFromRecord(recordPath)
si, err := os.Stat(fmt.Sprintf("/proc/%d", pid))
if err == nil {
    st := si.Sys().(*syscall.Stat_t)
    if int(st.Uid) != os.Getuid() {
        // will EPERM: escalate (sudo/container exec) or abandon before calling
    }
}

Type guard

func isSignalErr(err error) bool {
    return err != nil && strings.Contains(err.Error(), "signal pid ")
}

Try / catch

err := proxy.ForceStopUnverified(ctx, rootDir, pidName, deadline)
if isSignalErr(err) {
    if errors.Is(err, os.ErrPermission) {
        return fmt.Errorf("re-run with privileges of the daemon's owner (sudo or container exec)")
    }
    // ESRCH-style race: one retry resolves as ProcessWasGone
    report, err = proxy.ForceStopUnverified(ctx, rootDir, pidName, deadline)
    if err != nil { return err }
}

Prevention

When it happens

Trigger: proxy.ForceStopUnverified calling proc.kill() when the process is owned by another user (EPERM), exited in the microseconds between inspection and kill (ESRCH), or the platform signaling primitive (pidfd_send_signal, Windows TerminateProcess) returns an error.

Common situations: bd/dolt daemon started via sudo or in a container owned by root while force-stop runs as a normal user; extremely short-lived processes racing exit; restricted seccomp policies blocking signal syscalls; PID handed to a child re-parented to another user.

Related errors


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