{"record":{"id":"21a8d02ee3b2139b","repo":"gastownhall/beads","slug":"discover-spawned-proxy-w","errorCode":null,"errorMessage":"discover spawned proxy: %w","messagePattern":"discover spawned proxy: %w","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"internal/storage/dbproxy/proxy/endpoint.go","lineNumber":361,"sourceCode":"\t\t\t_ = child.handle.Close()\n\t\t}\n\t}()\n\n\thard := time.NewTimer(spawnReadyHardTimeout)\n\tdefer hard.Stop()\n\tpoll := time.NewTicker(openPollInterval)\n\tdefer poll.Stop()\n\n\tfor {\n\t\tdiscovered := readAndDial(rootDir)\n\t\tif discovered.status == adoptionAdopted {\n\t\t\tif err := sweepOldQuarantines(rootDir, time.Now()); err != nil {\n\t\t\t\tlog.Printf(\"dbproxy: could not sweep old quarantined records in %s: %v\", rootDir, err)\n\t\t\t}\n\t\t\treturn discovered.endpoint, nil\n\t\t}\n\t\tif discovered.status == adoptionIOErr {\n\t\t\treturn Endpoint{}, fmt.Errorf(\"discover spawned proxy: %w\", discovered.err)\n\t\t}\n\t\tselect {\n\t\tcase childErr := <-child.done:\n\t\t\tif interrupted, ierr := stopEpochChanged(rootDir, stopEpoch); ierr != nil {\n\t\t\t\treturn Endpoint{}, ierr\n\t\t\t} else if interrupted {\n\t\t\t\treturn Endpoint{}, fmt.Errorf(\"%w for %s\", errStartInterrupted, rootDir)\n\t\t\t}\n\t\t\tif childErr == nil {\n\t\t\t\tchildErr = errors.New(\"child exited without reporting an error\")\n\t\t\t}\n\t\t\t// A LockHeldExitCode exit is a lost spawn race, not a listen\n\t\t\t// failure; any other exit gets the child's log path so the real\n\t\t\t// error (listen, backend start, ...) is findable.\n\t\t\tvar exitErr *exec.ExitError\n\t\t\tif errors.As(childErr, &exitErr) && exitErr.ExitCode() == LockHeldExitCode {\n\t\t\t\treturn Endpoint{}, fmt.Errorf(\n\t\t\t\t\t\"proxy child lost the proxy.lock spawn race for %s: %w\",","sourceCodeStart":343,"sourceCodeEnd":379,"githubUrl":"https://github.com/gastownhall/beads/blob/71377f276968b452ee607177637970a4ff888584/internal/storage/dbproxy/proxy/endpoint.go#L343-L379","documentation":"After successfully forking the child proxy, the parent polls by re-reading the pidfile (readAndDial) until the child records its endpoint. If that post-spawn discovery read fails with an I/O error, the parent cannot confirm the child started, so it aborts and wraps the read error. This differs from the timeout path: the record was there but unreadable during the confirmation window.","triggerScenarios":"During the wait loop inside spawnAndHandoff, discovered.status == adoptionIOErr — the newly spawned child's pidfile cannot be read (permission flip, partial write being read concurrently, FS error) while waiting for the endpoint to appear.","commonSituations":"Slow network filesystems where the record appears but reads fail transiently; security software interfering with newly created files; child crashing mid-write leaving a torn record that then reads as an IO error.","solutions":["Retry the open once — transient read races usually clear on the next poll cycle","Check whether the child actually started and its log (opts.LogFilePath) for crash details","Fix permissions on the workspace pidfile path for the running user","If persistent, move the workspace to a local (non-NFS) filesystem"],"exampleFix":"// before\nep, err := proxy.GetCreateDatabaseProxyServerEndpoint(root, opts) // fails once on NFS hiccup\n// after\nep, err := proxy.GetCreateDatabaseProxyServerEndpoint(root, opts)\nif err != nil && errors.Is(err, io.ErrPermission) || isTransientIO(err) {\n    time.Sleep(time.Second)\n    ep, err = proxy.GetCreateDatabaseProxyServerEndpoint(root, opts)\n}","handlingStrategy":"retry","validationCode":null,"typeGuard":null,"tryCatchPattern":"ep, err := proxy.GetCreateDatabaseProxyServerEndpoint(root, opts)\nif err != nil && strings.HasPrefix(err.Error(), \"discover spawned proxy:\") {\n    time.Sleep(1 * time.Second)             // transient pidfile read race\n    ep, err = proxy.GetCreateDatabaseProxyServerEndpoint(root, opts)\n}","preventionTips":["Prefer local disks over NFS for workspaces","Ensure the child's LogFilePath is set so child crashes are visible","Exclude workspace files from antivirus/indexers"],"tags":["io","pidfile","spawn","race"],"backgroundTag":"pidfile-io-error","analyzedSha":"71377f276968b452ee607177637970a4ff888584","analyzedAt":"2026-08-30T18:55:39.744Z","schemaVersion":2},"datasetVersion":"2026-08-30T23:17:21.991Z"}