gastownhall/beads · error

child exited without reporting an error

Error message

child exited without reporting an error

What it means

This error replaces a nil child error when the managed proxy child process exits without reporting a failure, preventing an uninformative nil from masking a real startup problem. spawnAndHandoff raises it when the child died but wrote no error; the actual cause is then located via the child's log path (attached for non-LockHeldExitCode exits).

Source

Thrown at internal/storage/dbproxy/proxy/endpoint.go:371

		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",
					rootDir, childErr,
				)
			}
			if opts.Port != 0 {
				return Endpoint{}, fmt.Errorf(
					"proxy child exited before becoming ready on explicitly configured port %d (see %s): %w",
					opts.Port, opts.LogFilePath, childErr,
				)
			}
			return Endpoint{}, fmt.Errorf(

View on GitHub (pinned to 71377f2769)

Solutions

  1. Inspect the child log path included in the wrapped error to find the real failure
  2. Re-run the start after checking resources (memory, disk, binary availability)
  3. If ExitCode is LockHeldExitCode, treat it as a lost spawn race and retry instead

Example fix

// before
ep, err := GetCreateDatabaseProxyServerEndpoint(ctx, root)
if err != nil { return err }
// after
ep, err := GetCreateDatabaseProxyServerEndpoint(ctx, root)
if err != nil {
    var ee *exec.ExitError
    if errors.As(err, &ee) && ee.ExitCode() == LockHeldExitCode {
        return GetCreateDatabaseProxyServerEndpoint(ctx, root) // lost race, retry
    }
    return fmt.Errorf("proxy spawn failed: %w", err) // err mentions child log path
}
Defensive patterns

Strategy: try-catch

Validate before calling

if _, err := os.Stat(childLogPath); err == nil { // inspect log before retrying }

Type guard

func isSilentChildExit(err error) bool { return strings.Contains(err.Error(), "child exited without reporting an error") }

Try / catch

ep, err := GetCreateDatabaseProxyServerEndpoint(ctx, root)
if err != nil {
    var ee *exec.ExitError
    if errors.As(err, &ee) && ee.ExitCode() == LockHeldExitCode { return retry() }
    log.Errorf("proxy spawn failed (see child log in error): %v", err)
    return err
}

Prevention

When it happens

Trigger: Child proxy process exits during spawnAndHandoff with exit code other than LockHeldExitCode and without writing its error to the handoff channel or log.

Common situations: Child killed by a signal (OOM killer, external kill) before it could report; binary crash before logging; environment problems (bad PATH, missing Dolt binary) causing silent early exit.

Related errors


AI-assisted analysis of gastownhall/beads@71377f2769 (2026-08-30). Data as JSON: /api/errors/f0ccdc34348c054c. Report an issue: GitHub.