{"record":{"id":"d05b1bc485d53210","repo":"gastownhall/beads","slug":"read-proxy-stop-epoch-under-lock-w","errorCode":null,"errorMessage":"read proxy stop epoch under lock: %w","messagePattern":"read proxy stop epoch under lock: %w","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"internal/storage/dbproxy/proxy/endpoint.go","lineNumber":322,"sourceCode":"\nfunc spawnAndHandoff(\n\trootDir string,\n\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) }()","sourceCodeStart":304,"sourceCodeEnd":340,"githubUrl":"https://github.com/gastownhall/beads/blob/71377f276968b452ee607177637970a4ff888584/internal/storage/dbproxy/proxy/endpoint.go#L304-L340","documentation":"spawnAndHandoff, while holding proxy.lock, re-reads the stop epoch file to confirm no concurrent 'stop' happened between the caller's initial read and the spawn. If readStopEpoch fails here (I/O error on the epoch file), the spawn is aborted and the read error is wrapped and returned. This guards against spawning a proxy that a concurrent stop already intended to tear down.","triggerScenarios":"A spawn attempt is in progress (spawnAndHandoff under lock) and readStopEpoch(rootDir) returns an error — the stop-epoch file is unreadable, has bad permissions, or the storage is failing at that moment.","commonSituations":"Concurrent bd stop/restart mutating files in rootDir during a spawn; read-only remount triggered mid-operation (e.g. disk error flipping the FS); permission changes by security tooling; workspace on a flaky network mount.","solutions":["Retry the operation once the filesystem is readable; transient IO errors often clear","Check permissions/health of the stop-epoch file in <rootDir> (ls -l, cat it manually)","Ensure no concurrent bd stop/teardown is racing your spawn (serialize commands)","Move the workspace off a failing/network filesystem if errors persist"],"exampleFix":"// before\nerror: read proxy stop epoch under lock: open /repo/.beads/stop-epoch: permission denied\n// after\n$ chmod u+r /repo/.beads/stop-epoch\n$ bd ready","handlingStrategy":"retry","validationCode":"// Verify the stop-epoch file is readable before spawning work\nif _, err := os.ReadFile(filepath.Join(rootDir, \"stop-epoch\")); err != nil {\n    return fmt.Errorf(\"cannot read stop epoch: %w\", err)\n}","typeGuard":null,"tryCatchPattern":"ep, err := proxy.GetCreateDatabaseProxyServerEndpoint(root, opts)\nif err != nil && strings.HasPrefix(err.Error(), \"read proxy stop epoch under lock:\") {\n    time.Sleep(time.Second)\n    ep, err = proxy.GetCreateDatabaseProxyServerEndpoint(root, opts) // transient IO often clears\n}","preventionTips":["Serialize bd stop/start scripts","Keep workspaces on reliable local storage","Fix permission drift from security tooling"],"tags":["io","epoch","lock","race"],"backgroundTag":"pidfile-io-error","analyzedSha":"71377f276968b452ee607177637970a4ff888584","analyzedAt":"2026-08-30T18:55:39.744Z","schemaVersion":2},"datasetVersion":"2026-08-30T23:17:21.991Z"}