gastownhall/beads · warning · errStartInterrupted
%w for %s: stop epoch advanced during startup
Error message
%w for %s: stop epoch advanced during startup
What it means
During startup, if the stop epoch advances after the listener/backend boot began but before startup completes, the proxy invokes abortInterruptedStart, which stops the backend, releases proxy.lock early, and returns this errStartInterrupted-wrapped error. Nothing has been published yet, so a clean abort avoids starving the stopper into its timeout.
Source
Thrown at internal/storage/dbproxy/proxy/server.go:220
}
}
}()
stopEpochWatch := func() {
epochWatchCancel()
<-epochWatchDone
}
defer stopEpochWatch()
// abortInterruptedStart tears down a doomed start whose stop epoch
// advanced mid-boot. The interrupting stop is polling proxy.lock under a
// budget (shutdownConfirmDeadline) far smaller than a backend stop can
// take, and nothing has been published, so release the lock BEFORE the
// backend teardown instead of starving the stopper into its timeout.
abortInterruptedStart := func() error {
p.stats.IncBackendStop()
releaseLock()
_ = stopBackendBounded(p.server)
return fmt.Errorf("%w for %s: stop epoch advanced during startup", errStartInterrupted, p.rootDir)
}
addr := fmt.Sprintf("127.0.0.1:%d", p.port)
ln, err := net.Listen("tcp", addr)
if err != nil {
return fmt.Errorf("listen on %s: %w", addr, err)
}
p.listener = ln
defer func() { _ = ln.Close() }()
p.stats.IncListenAndServe()
dataPort, ok := ln.Addr().(*net.TCPAddr)
if !ok {
return fmt.Errorf("proxy: unexpected data listener address %T", ln.Addr())
}
if _, err := identity.WriteSecret(p.rootDir); err != nil {View on GitHub (pinned to 71377f2769)
Solutions
- Re-run the start after the stop has fully completed
- Sequence start/stop in automation so they don't overlap
- Treat errStartInterrupted as an expected abort: verify the service is stopped rather than retrying blindly
Defensive patterns
Strategy: retry
Validate before calling
// verify the stop epoch is stable before starting
epoch1, _ := readStopEpoch(rootDir)
time.Sleep(50 * time.Millisecond)
epoch2, err := readStopEpoch(rootDir)
if err != nil || epoch1 != epoch2 { return fmt.Errorf("stop in flight") } Try / catch
err := p.ListenAndServe(ctx)
if errors.Is(err, errStartInterrupted) { /* stop won the race; decide whether to restart */ } Prevention
- Avoid overlapping start/stop commands in automation
- Add small settle delays between stop and restart in tests
- Treat errStartInterrupted as an expected clean abort, not a bug
When it happens
Trigger: `bd dolt stop` advances the stop epoch while the proxy is mid-startup (after the post-lock epoch check, before publishing); detected via the startup-completion epoch check.
Common situations: Stop command racing an auto-restart; CI/tests issuing rapid start/stop cycles; an operator stopping the service at the same moment a supervisor restarts it.
Related errors
- %w for %s: stop epoch advanced before startup
- target git directory identity changed
- check proxy stop epoch after acquiring %s: %w
- failed to unclaim issue %s: no matching row
- lock busy: held by another process
AI-assisted analysis of gastownhall/beads@71377f2769 (2026-08-30).
Data as JSON: /api/errors/54a0fb8d3ee5d8c3.
Report an issue: GitHub.