gastownhall/beads · error
check proxy stop epoch after acquiring %s: %w
Error message
check proxy stop epoch after acquiring %s: %w
What it means
After taking proxy.lock, the child re-checks the stop epoch to detect a concurrent `bd dolt stop` that advanced it while the child was waiting for the lock. If reading/comparing the epoch fails, startup aborts with this wrapped error; it must not boot a backend on unknown epoch state.
Source
Thrown at internal/storage/dbproxy/proxy/server.go:140
releaseLock := func() {
if lockHeld {
lockHeld = false
lock.Unlock()
}
}
defer releaseLock()
if err := clearSpawnMarkerAfterLock(p.rootDir); err != nil {
return fmt.Errorf("clear proxy spawn marker: %w", err)
}
// Fast-abort: a concurrent `bd dolt stop` advances the stop epoch before
// waiting (briefly) for proxy.lock, so an epoch that moved between the
// spawning parent's read and this child taking the lock dooms this start.
// Abort before opening any listener or booting the backend: the stopper
// then observes a free lock within milliseconds instead of after a full
// doomed boot-and-teardown cycle.
if changed, err := stopEpochChanged(p.rootDir, p.stopEpoch); err != nil {
return fmt.Errorf("check proxy stop epoch after acquiring %s: %w", LockFileName, err)
} else if changed {
return fmt.Errorf("%w for %s: stop epoch advanced before startup", errStartInterrupted, p.rootDir)
}
logPath := filepath.Join(p.rootDir, LogFileName)
f, err := os.OpenFile(logPath, os.O_CREATE|os.O_APPEND|os.O_WRONLY, 0o600) // #nosec G304 -- logPath is derived from operator-supplied config, not untrusted request input
if err != nil {
return fmt.Errorf("open proxy log %q: %w", logPath, err)
}
p.logger = log.New(f, "[proxy] ", log.LstdFlags|log.Lmicroseconds)
defer func() { _ = f.Close() }()
ctx, cancel := context.WithCancel(parentCtx)
defer cancel()
// Install signal handlers BEFORE Listen. Without this, Go's default
// SIGTERM action terminates the process during the startup window
// (Listen, pidfile write, backend Start, readiness wait), bypassing allView on GitHub (pinned to 71377f2769)
Solutions
- Retry starting the proxy once the concurrent stop has completed
- Fix permissions/availability of the epoch file in rootDir
- Stop any concurrent `bd dolt stop` processes racing the start
Defensive patterns
Strategy: retry
Validate before calling
// ensure no stop is in flight before starting
if epoch, err := readStopEpoch(rootDir); err != nil { return err } // pre-read sanity Try / catch
if err := p.ListenAndServe(ctx); err != nil {
if strings.Contains(err.Error(), "stop epoch") { /* stop finished; retry start */ }
return err
} Prevention
- Don't run bd dolt stop concurrently with starts
- Keep rootDir state files readable by the daemon user
- Retry starts after concurrent stops complete
When it happens
Trigger: ListenAndServe calls stopEpochChanged(p.rootDir, p.stopEpoch) right after locking, and the epoch read/compare returns an error (unreadable epoch file, I/O failure).
Common situations: Concurrent stop corrupting or removing the epoch file mid-read; permission issues on rootDir state files; disk I/O errors.
Related errors
- errStartInterrupted
- acquire %s: %w
- %w for %s: stop epoch advanced before startup
- %w for %s: stop epoch advanced during startup
- %w for %s: stop epoch advanced during backend start (%v)
AI-assisted analysis of gastownhall/beads@71377f2769 (2026-08-30).
Data as JSON: /api/errors/f739b24a93eaf544.
Report an issue: GitHub.