gastownhall/beads · error

refusing backend cleanup: %s is held while proxy lock is fre

Error message

refusing backend cleanup: %s is held while proxy lock is free (recorded pid %d at %s); stop the process holding the child lock or remove the stale lock owner before retrying

What it means

cleanupOrphanBackend refuses to clean up the orphaned backend when its child lock file (server lock) is still held while the proxy lock is free. This is a safety guard: some process may still be using the backend, so removing its record or stopping it would be unsafe. The error names the lock path, the recorded pid, and the record path.

Source

Thrown at internal/storage/dbproxy/proxy/endpoint.go:761

	}
	return errors.Join(errs...)
}

func cleanupOrphanBackend(rootDir string) error {
	recordPath := pidfile.Path(rootDir, server.PIDFileName)
	pf, readErr := pidfile.Read(rootDir, server.PIDFileName)

	childLockPath := filepath.Join(rootDir, server.LockFileName)
	childLock, lockErr := util.TryLock(childLockPath)
	switch {
	case lockErr == nil:
		childLock.Unlock()
	case lockfile.IsLocked(lockErr):
		pid := 0
		if pf != nil {
			pid = pf.Pid
		}
		return fmt.Errorf(
			"refusing backend cleanup: %s is held while proxy lock is free (recorded pid %d at %s); stop the process holding the child lock or remove the stale lock owner before retrying",
			childLockPath, pid, recordPath,
		)
	default:
		return fmt.Errorf("probe backend lock %s: %w", childLockPath, lockErr)
	}

	if readErr != nil {
		if isMalformedPIDFileError(readErr) {
			return unverifiableProcessError(
				"backend cleanup",
				recordPath,
				0,
				readErr,
				unverifiableProcessChecks{},
			)
		}
		return fmt.Errorf("read backend record %s: %w", recordPath, readErr)

View on GitHub (pinned to 71377f2769)

Solutions

  1. Check whether pid <pid> is alive and what it is (ps -fp <pid>); if it's a live bd session, let it finish before restarting the proxy
  2. If the recorded pid is dead but the lock seems held, remove the stale lock owner per the message — verify no live holder with lsof <childLockPath> or fuser, then delete the lock file
  3. Ensure only one bd workspace session is active (close other terminals/tools using the same beads workspace)
  4. Re-run bd after the holder exits — cleanup will succeed once the child lock is free

Example fix

// before (shell)
bd doctor --fix
// after: confirm and clear the stale holder first
lsof /path/to/workspace/.beads/server.lock   # identify holder
ps -fp <pid>                                  # confirm it is stale or another session
# if stale/foreign, remove the lock and retry
rm -f /path/to/workspace/.beads/server.lock
bd doctor --fix
Defensive patterns

Strategy: validation

Validate before calling

// check the holder before asking for backend cleanup
pf, _ := pidfile.Read(root, server.PIDFileName)
if pf != nil && pidAlive(pf.Pid) {
    return fmt.Errorf("backend pid %d still alive; stop it before cleanup", pf.Pid)
}
out, _ := exec.Command("lsof", "-wn", root+"/.beads/server.lock").Output()
fmt.Println(string(out)) // confirm nobody holds the lock

Try / catch

err := cleanupOrphanBackend(root)
var held *lockHeldError
if err != nil && strings.Contains(err.Error(), "refusing backend cleanup") {
    // parse recorded pid from the message, verify liveness, then stop holder or clear stale lock
}

Prevention

When it happens

Trigger: During spawnAndHandoff's backend cleanup, util.TryLock(childLockPath) returns lockfile.IsLocked while the proxy lock could be acquired — a live or lock-leaking process holds the backend's lock.

Common situations: A crashed bd child leaked an OS-level file lock that the OS hasn't released (rare; flock is released on process death, so more likely another live process); a second bd session is actively using the same workspace; container PID-namespace confusion making the recorded pid unresolvable; an unrelated process holding the lock file open with an exclusive lock.

Related errors


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