{"record":{"id":"b5a69bdb0f7afcaa","repo":"gastownhall/beads","slug":"w-for-s","errorCode":null,"errorMessage":"%w for %s","messagePattern":"%w for (.+?)","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"warning","filePath":"internal/storage/dbproxy/proxy/endpoint.go","lineNumber":325,"sourceCode":"\topts OpenOpts,\n\tdeadline time.Time,\n\tstopEpoch string,\n\tlock *util.Lock,\n\tdiscovery adoptionResult,\n) (Endpoint, error) {\n\thandedOff := false\n\tdefer func() {\n\t\tif !handedOff {\n\t\t\tlock.Unlock()\n\t\t}\n\t}()\n\n\tcurrentEpoch, err := readStopEpoch(rootDir)\n\tif err != nil {\n\t\treturn Endpoint{}, fmt.Errorf(\"read proxy stop epoch under lock: %w\", err)\n\t}\n\tif currentEpoch != stopEpoch {\n\t\treturn Endpoint{}, fmt.Errorf(\"%w for %s\", errStartInterrupted, rootDir)\n\t}\n\n\tif err := quarantineForSpawn(rootDir, discovery); err != nil {\n\t\treturn Endpoint{}, err\n\t}\n\tif err := cleanupOrphanBackend(rootDir); err != nil {\n\t\treturn Endpoint{}, err\n\t}\n\n\thandedOff = true\n\tchild, err := forkExecChild(rootDir, opts, opts.Port, stopEpoch, lock)\n\tif err != nil {\n\t\treturn Endpoint{}, fmt.Errorf(\"fork child: %w\", err)\n\t}\n\tdefer func() { _ = clearOwnSpawnMarker(rootDir, child.marker) }()\n\tdefer func() {\n\t\tif child.handle != nil {\n\t\t\t_ = child.handle.Close()","sourceCodeStart":307,"sourceCodeEnd":343,"githubUrl":"https://github.com/gastownhall/beads/blob/71377f276968b452ee607177637970a4ff888584/internal/storage/dbproxy/proxy/endpoint.go#L307-L343","documentation":"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.","triggerScenarios":"Concurrent bd stop / restart / teardown while GetCreateDatabaseProxyServerEndpoint is mid-spawn: currentEpoch (read under lock) != stopEpoch (read at function entry).","commonSituations":"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.","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"],"exampleFix":"// before\nep, err := proxy.GetCreateDatabaseProxyServerEndpoint(root, opts) // once, abort on error\n// after\nfor i := 0; i < 3; i++ {\n    ep, err := proxy.GetCreateDatabaseProxyServerEndpoint(root, opts)\n    if err == nil || !errors.Is(err, proxy.ErrStartInterrupted) {\n        break\n    }\n    time.Sleep(500 * time.Millisecond)\n}","handlingStrategy":"retry","validationCode":null,"typeGuard":null,"tryCatchPattern":"ep, err := proxy.GetCreateDatabaseProxyServerEndpoint(root, opts)\nif errors.Is(err, proxy.ErrStartInterrupted) {\n    time.Sleep(250 * time.Millisecond)      // let the concurrent stop settle\n    ep, err = proxy.GetCreateDatabaseProxyServerEndpoint(root, opts)\n}","preventionTips":["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"],"tags":["race","concurrency","epoch","retry"],"backgroundTag":"start-interrupted-race","analyzedSha":"71377f276968b452ee607177637970a4ff888584","analyzedAt":"2026-08-30T18:55:39.744Z","schemaVersion":2},"datasetVersion":"2026-08-30T23:17:21.991Z"}