{"record":{"id":"d32415558299cb71","repo":"gastownhall/beads","slug":"w-for-s-stop-epoch-advanced-before-startup","errorCode":null,"errorMessage":"%w for %s: stop epoch advanced before startup","messagePattern":"%w for (.+?): stop epoch advanced before startup","errorType":"exception","errorClass":"errStartInterrupted","httpStatus":null,"severity":"warning","filePath":"internal/storage/dbproxy/proxy/server.go","lineNumber":142,"sourceCode":"\t\t\tlockHeld = false\n\t\t\tlock.Unlock()\n\t\t}\n\t}\n\tdefer releaseLock()\n\tif err := clearSpawnMarkerAfterLock(p.rootDir); err != nil {\n\t\treturn fmt.Errorf(\"clear proxy spawn marker: %w\", err)\n\t}\n\n\t// Fast-abort: a concurrent `bd dolt stop` advances the stop epoch before\n\t// waiting (briefly) for proxy.lock, so an epoch that moved between the\n\t// spawning parent's read and this child taking the lock dooms this start.\n\t// Abort before opening any listener or booting the backend: the stopper\n\t// then observes a free lock within milliseconds instead of after a full\n\t// doomed boot-and-teardown cycle.\n\tif changed, err := stopEpochChanged(p.rootDir, p.stopEpoch); err != nil {\n\t\treturn fmt.Errorf(\"check proxy stop epoch after acquiring %s: %w\", LockFileName, err)\n\t} else if changed {\n\t\treturn fmt.Errorf(\"%w for %s: stop epoch advanced before startup\", errStartInterrupted, p.rootDir)\n\t}\n\n\tlogPath := filepath.Join(p.rootDir, LogFileName)\n\tf, 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\n\tif err != nil {\n\t\treturn fmt.Errorf(\"open proxy log %q: %w\", logPath, err)\n\t}\n\tp.logger = log.New(f, \"[proxy] \", log.LstdFlags|log.Lmicroseconds)\n\tdefer func() { _ = f.Close() }()\n\n\tctx, cancel := context.WithCancel(parentCtx)\n\tdefer cancel()\n\n\t// Install signal handlers BEFORE Listen. Without this, Go's default\n\t// SIGTERM action terminates the process during the startup window\n\t// (Listen, pidfile write, backend Start, readiness wait), bypassing all\n\t// deferred cleanup including RemoveDatabaseProxyPidFile.\n\tsigCh := make(chan os.Signal, 1)","sourceCodeStart":124,"sourceCodeEnd":160,"githubUrl":"https://github.com/gastownhall/beads/blob/71377f276968b452ee607177637970a4ff888584/internal/storage/dbproxy/proxy/server.go#L124-L160","documentation":"If the stop epoch advanced between the parent's read and this child acquiring proxy.lock, the start is doomed — a stopper is already waiting. The proxy returns this error (wrapping errStartInterrupted) before opening any listener or booting the backend so the stopper observes a free lock within milliseconds instead of after a full boot-and-teardown cycle.","triggerScenarios":"ListenAndServe detects stopEpochChanged == true immediately after acquiring proxy.lock — i.e. `bd dolt stop` ran concurrently with the spawn.","commonSituations":"Race between a script that starts the daemon and one that stops it; test suites running start/stop concurrently; operator stopping a service right as an auto-restart spawns it.","solutions":["Re-run the start command now that the stop has finished","Coordinate start/stop in scripts (wait for stop to complete before starting)","If restart managers are involved, disable auto-restart during intentional stops"],"exampleFix":null,"handlingStrategy":"retry","validationCode":"// check epoch before spawning\nepoch, err := readStopEpoch(rootDir)\nif err != nil { return err }\n_ = epoch // pass to child; mismatch means abort","typeGuard":null,"tryCatchPattern":"err := p.ListenAndServe(ctx)\nif errors.Is(err, errStartInterrupted) { /* expected during concurrent stop; restart if desired */ }","preventionTips":["Serialize start/stop in scripts and supervisors","Disable auto-restart during intentional stops","Treat errStartInterrupted as normal during stop windows"],"tags":["concurrency","stop-epoch","race-condition"],"backgroundTag":"stop-epoch-conflict","analyzedSha":"71377f276968b452ee607177637970a4ff888584","analyzedAt":"2026-08-30T18:55:39.744Z","schemaVersion":2},"datasetVersion":"2026-08-30T23:17:21.991Z"}