gastownhall/beads · warning
%w for %s
Error message
%w for %s
What it means
Between the caller's initial stop-epoch read and the locked spawn, the stop epoch changed — meaning another process issued a proxy stop (or a restart cycle) concurrently. spawnAndHandoff aborts rather than spawn a proxy that was just asked to die, returning errStartInterrupted wrapped with the rootDir. Callers detect this via errors.Is(err, errStartInterrupted) and typically retry the whole open, which will re-read the fresh epoch.
Source
Thrown at internal/storage/dbproxy/proxy/endpoint.go:325
opts OpenOpts,
deadline time.Time,
stopEpoch string,
lock *util.Lock,
discovery adoptionResult,
) (Endpoint, error) {
handedOff := false
defer func() {
if !handedOff {
lock.Unlock()
}
}()
currentEpoch, err := readStopEpoch(rootDir)
if err != nil {
return Endpoint{}, fmt.Errorf("read proxy stop epoch under lock: %w", err)
}
if currentEpoch != stopEpoch {
return Endpoint{}, fmt.Errorf("%w for %s", errStartInterrupted, rootDir)
}
if err := quarantineForSpawn(rootDir, discovery); err != nil {
return Endpoint{}, err
}
if err := cleanupOrphanBackend(rootDir); err != nil {
return Endpoint{}, err
}
handedOff = true
child, err := forkExecChild(rootDir, opts, opts.Port, stopEpoch, lock)
if err != nil {
return Endpoint{}, fmt.Errorf("fork child: %w", err)
}
defer func() { _ = clearOwnSpawnMarker(rootDir, child.marker) }()
defer func() {
if child.handle != nil {
_ = child.handle.Close()View on GitHub (pinned to 71377f2769)
Solutions
- Simply retry the operation — this is a benign race; the next open reads the new epoch
- Avoid concurrent bd stop/start scripts on the same workspace; serialize via a wrapper
- If it loops, find what keeps issuing stops (supervisor, watchdog, second CLI) and pause it
Example fix
// before
ep, err := proxy.GetCreateDatabaseProxyServerEndpoint(root, opts) // once, abort on error
// after
for i := 0; i < 3; i++ {
ep, err := proxy.GetCreateDatabaseProxyServerEndpoint(root, opts)
if err == nil || !errors.Is(err, proxy.ErrStartInterrupted) {
break
}
time.Sleep(500 * time.Millisecond)
} Defensive patterns
Strategy: retry
Try / catch
ep, err := proxy.GetCreateDatabaseProxyServerEndpoint(root, opts)
if errors.Is(err, proxy.ErrStartInterrupted) {
time.Sleep(250 * time.Millisecond) // let the concurrent stop settle
ep, err = proxy.GetCreateDatabaseProxyServerEndpoint(root, opts)
} Prevention
- Do not run bd stop concurrently with bd open on the same workspace
- Use a single supervisor for proxy lifecycle
- Treat ErrStartInterrupted as retryable, not fatal
When it happens
Trigger: Concurrent bd stop / restart / teardown while GetCreateDatabaseProxyServerEndpoint is mid-spawn: currentEpoch (read under lock) != stopEpoch (read at function entry).
Common situations: A test harness or script restarting bd while another command opens the DB; IDE plugin and CLI fighting over the same workspace; supervisor restarts racing user commands.
Related errors
- errStartInterrupted
- ErrBusy
- read proxy stop epoch under lock: %w
- timeout (%s) waiting for spawn marker %s; wait for the in-pr
- %w: %w
AI-assisted analysis of gastownhall/beads@71377f2769 (2026-08-30).
Data as JSON: /api/errors/b5a69bdb0f7afcaa.
Report an issue: GitHub.