gastownhall/beads · error

probe backend lock %s: %w

Error message

probe backend lock %s: %w

What it means

cleanupOrphanBackend wraps unexpected errors from util.TryLock on the backend child lock file. This is neither success nor a clean 'locked' answer, so the probe itself failed and backend cleanup cannot safely proceed.

Source

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

	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)
	}
	if pf == nil {
		return nil
	}
	if err := pf.ValidateV2(pidfile.KindDoltBackend); err != nil {

View on GitHub (pinned to 71377f2769)

Solutions

  1. Inspect the wrapped OS error (errors.Unwrap) — it names the failing syscall on the lock path
  2. Fix permissions/ownership of the lock file and its directory (chown/chmod, remove a root-owned server.lock if appropriate)
  3. Verify the workspace is on a filesystem supporting flock (avoid NFS without local locking; move workspace to local disk)
  4. Confirm the lock path is a regular file (rm a wrongly-typed directory entry named like the lock) and re-run

Example fix

// before
childLock, lockErr := util.TryLock(childLockPath)
...
default:
    return fmt.Errorf("probe backend lock %s: %w", childLockPath, lockErr)
// after (caller)
if err := cleanupOrphanBackend(root); err != nil && strings.HasPrefix(err.Error(), "probe backend lock") {
    st, statErr := os.Stat(childLockPath)
    fmt.Printf("lock probe failed: %v (path stat: %v, %v)\n", err, statErr, st)
}
Defensive patterns

Strategy: validation

Validate before calling

lp := root + "/.beads/server.lock"
if fi, err := os.Stat(lp); err != nil {
    if !errors.Is(err, fs.ErrNotExist) {
        return fmt.Errorf("lock path unusable: %w", err)
    }
} else if !fi.Mode().IsRegular() {
    return fmt.Errorf("%s is not a regular file", lp)
}
if f, err := os.OpenFile(lp, os.O_RDWR|os.O_CREATE, 0o600); err != nil {
    return fmt.Errorf("cannot open lock file: %w", err)
} else { f.Close() }

Try / catch

err := cleanupOrphanBackend(root)
if err != nil && strings.HasPrefix(err.Error(), "probe backend lock ") {
    // wrapped OS error names the failing syscall: fix perms/filesystem, then retry
}

Prevention

When it happens

Trigger: util.TryLock(childLockPath) returns an OS error other than EWOULDBLOCK-style 'is locked' — e.g. permission denied opening/creating the lock file, or path errors (component is not a directory, I/O error on the workspace).

Common situations: The lock file path exists as a directory or is otherwise unwritable; workspace mounted read-only or on a filesystem without flock support (some network FS); permission mismatch after running bd as different users (root-owned lock file).

Related errors


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