gastownhall/beads · error
discover spawned proxy: %w
Error message
discover spawned proxy: %w
What it means
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.
Source
Thrown at internal/storage/dbproxy/proxy/endpoint.go:361
_ = child.handle.Close()
}
}()
hard := time.NewTimer(spawnReadyHardTimeout)
defer hard.Stop()
poll := time.NewTicker(openPollInterval)
defer poll.Stop()
for {
discovered := readAndDial(rootDir)
if discovered.status == adoptionAdopted {
if err := sweepOldQuarantines(rootDir, time.Now()); err != nil {
log.Printf("dbproxy: could not sweep old quarantined records in %s: %v", rootDir, err)
}
return discovered.endpoint, nil
}
if discovered.status == adoptionIOErr {
return Endpoint{}, fmt.Errorf("discover spawned proxy: %w", discovered.err)
}
select {
case childErr := <-child.done:
if interrupted, ierr := stopEpochChanged(rootDir, stopEpoch); ierr != nil {
return Endpoint{}, ierr
} else if interrupted {
return Endpoint{}, fmt.Errorf("%w for %s", errStartInterrupted, rootDir)
}
if childErr == nil {
childErr = errors.New("child exited without reporting an error")
}
// A LockHeldExitCode exit is a lost spawn race, not a listen
// failure; any other exit gets the child's log path so the real
// error (listen, backend start, ...) is findable.
var exitErr *exec.ExitError
if errors.As(childErr, &exitErr) && exitErr.ExitCode() == LockHeldExitCode {
return Endpoint{}, fmt.Errorf(
"proxy child lost the proxy.lock spawn race for %s: %w",View on GitHub (pinned to 71377f2769)
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
Example fix
// before
ep, err := proxy.GetCreateDatabaseProxyServerEndpoint(root, opts) // fails once on NFS hiccup
// after
ep, err := proxy.GetCreateDatabaseProxyServerEndpoint(root, opts)
if err != nil && errors.Is(err, io.ErrPermission) || isTransientIO(err) {
time.Sleep(time.Second)
ep, err = proxy.GetCreateDatabaseProxyServerEndpoint(root, opts)
} Defensive patterns
Strategy: retry
Try / catch
ep, err := proxy.GetCreateDatabaseProxyServerEndpoint(root, opts)
if err != nil && strings.HasPrefix(err.Error(), "discover spawned proxy:") {
time.Sleep(1 * time.Second) // transient pidfile read race
ep, err = proxy.GetCreateDatabaseProxyServerEndpoint(root, opts)
} Prevention
- 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
When it happens
Trigger: 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.
Common situations: 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.
Related errors
- discover proxy from %s: %w
- discover proxy from %s under lock: %w
- read proxy stop epoch under lock: %w
- cannot spawn from proxy discovery status %s
- read held-lock record %s: %w
AI-assisted analysis of gastownhall/beads@71377f2769 (2026-08-30).
Data as JSON: /api/errors/21a8d02ee3b2139b.
Report an issue: GitHub.