gastownhall/beads · error
legacy proxy record %s is protected by held lock %s; stop th
Error message
legacy proxy record %s is protected by held lock %s; stop the pre-upgrade proxy with the old bd binary or wait for its idle exit, then quarantine the record manually by renaming %s to %s.stale-<unix-timestamp> before retrying
What it means
The library found a legacy (pre-upgrade format) proxy record AND the proxy.lock is currently held by another process — almost certainly the old bd binary's proxy still running. The new code refuses to touch or replace a legacy record while a lock holder is alive, because the old binary may still be serving on it, so it returns this actionable message telling you how to drain and quarantine the record. It is a deliberate safety stop during binary upgrades, not a bug.
Source
Thrown at internal/storage/dbproxy/proxy/endpoint.go:274
lastSpawnErr = nil
// This break exits the switch only; the outer discovery loop
// continues with its normal bounded poll.
break
}
var ep Endpoint
ep, lastSpawnErr = spawnAndHandoff(rootDir, opts, deadline, stopEpoch, lock, discovery)
if lastSpawnErr == nil {
return ep, nil
}
if errors.Is(lastSpawnErr, errStartInterrupted) {
return Endpoint{}, lastSpawnErr
}
case !lockfile.IsLocked(err):
return Endpoint{}, fmt.Errorf("probe proxy lock: %w", err)
case discovery.status == adoptionLegacy:
recordPath := pidfile.Path(rootDir, PIDFileName)
return Endpoint{}, fmt.Errorf(
"legacy proxy record %s is protected by held lock %s; stop the pre-upgrade proxy with the old bd binary or wait for its idle exit, then quarantine the record manually by renaming %s to %s.stale-<unix-timestamp> before retrying",
recordPath,
filepath.Join(rootDir, LockFileName),
recordPath,
recordPath,
)
}
select {
case <-timeout.C:
if lastSpawnErr != nil {
return Endpoint{}, lastSpawnErr
}
return Endpoint{}, fmt.Errorf("timeout waiting for proxy on %s", rootDir)
case <-poll.C:
}
}
}View on GitHub (pinned to 71377f2769)
Solutions
- Stop the old proxy: run the pre-upgrade bd binary once so it shuts down, or kill the old bd process (pgrep -f bd)
- Wait for the old proxy's idle exit timeout to elapse, then retry
- If the proxy is definitely gone, quarantine manually: mv .beads/proxy.pid .beads/proxy.pid.stale-$(date +%s) and retry
- Pin all tooling (CI + local) to the same bd version to avoid mixed-version proxies
Example fix
// before error: legacy proxy record /repo/.beads/proxy.pid is protected by held lock /repo/.beads/proxy.lock; ... // after $ pgrep -af 'bd.*serve' # find old proxy $ kill <old-pid> # or wait for idle exit $ bd ready # succeeds; or mv .beads/proxy.pid .beads/proxy.pid.stale-$(date +%s) first
Defensive patterns
Strategy: validation
Validate before calling
// Detect a legacy record + held lock before attempting open
if _, err := os.Stat(filepath.Join(rootDir, "proxy.pid")); err == nil {
lf, err := os.Open(filepath.Join(rootDir, "proxy.lock"))
if err == nil {
defer lf.Close()
if err := syscall.Flock(int(lf.Fd()), syscall.LOCK_EX|syscall.LOCK_NB); err == syscall.EWOULDBLOCK {
return errors.New("old proxy still running; upgrade blocked")
}
}
} Prevention
- Stop all background bd proxies before upgrading the binary
- Pin the bd version across CI, IDE plugins, and local shells
- After upgrades, run one command with the new binary and allow the idle-exit window to pass
When it happens
Trigger: GetCreateDatabaseProxyServerEndpoint sees discovery.status == adoptionLegacy while TryLock reports the lock is held (IsLocked). Happens when an older bd binary spawned a proxy, then the new binary tries to open the same workspace before the old proxy exits.
Common situations: Upgrading bd while a long-lived background proxy from the previous version is still running; idle-exit timeout of the old proxy has not elapsed; two different bd versions interleaved in the same clone (e.g. CI pinned to old version, dev on new).
Related errors
- discover proxy from %s under lock: %w
- probe proxy lock: %w
- pidfile: legacy schema
- clone from %s succeeded, but the database needs %d schema %s
- proxied-server provider %T does not offer the issue-count su
AI-assisted analysis of gastownhall/beads@71377f2769 (2026-08-30).
Data as JSON: /api/errors/3a746a787ac49b25.
Report an issue: GitHub.