gastownhall/beads · error

fork child

Error message

fork child: %w

What it means

The library failed to fork/exec the proxy child process (forkExecChild). This wraps the OS-level spawn failure — binary missing, exec format or permission problem, or resource limits — after the lock and quarantine steps already succeeded. Everything spawned so far is cleaned up (handoff flag, marker, lock) before returning.

Solutions

  1. Verify the configured binary path exists and is executable: ls -l $(DoltBinPath) and try running it directly
  2. Check the mount is not noexec (mount output) and fix the binary location
  3. Raise limits (ulimit -u, cgroup pids.max) if fork fails under load
  4. Reinstall/upgrade bd so the bundled paths match the current version

Example fix

// before
opts.DoltBinPath = "/usr/local/bin/dolt" // binary removed by upgrade
// error: fork child: fork/exec /usr/local/bin/dolt: no such file or directory
// after
if _, err := os.Stat(opts.DoltBinPath); err != nil {
    opts.DoltBinPath = "/opt/bd/bin/dolt" // re-point at the real binary
}
ep, err := proxy.GetCreateDatabaseProxyServerEndpoint(root, opts)
Defensive patterns

Strategy: validation

Validate before calling

// Verify the spawn binary before opening
if opts.DoltBinPath != "" {
    if fi, err := os.Stat(opts.DoltBinPath); err != nil || fi.IsDir() || fi.Mode().Perm()&0o100 == 0 {
        return fmt.Errorf("spawn binary missing or not executable: %s", opts.DoltBinPath)
    }
}

Prevention

When it happens

Trigger: forkExecChild(rootDir, opts, opts.Port, stopEpoch, lock) fails during spawnAndHandoff: opts.DoltBinPath (or the proxy entrypoint binary) does not exist or is not executable, exec fails due to ENOEXEC/EACCES, or fork hits resource limits (ulimit, PID exhaustion).

Common situations: bd upgraded/downgraded and the configured DoltBinPath points to an old or removed binary; container images missing the dolt binary; noexec mount on the binary's filesystem; hitting process limits in CI sandboxes.

Related errors


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

Appendix: source

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

	currentEpoch, err := readStopEpoch(rootDir)
	if err != nil {
		return Endpoint{}, fmt.Errorf("read proxy stop epoch under lock: %w", err)
	}
	if currentEpoch != stopEpoch {
		return Endpoint{}, fmt.Errorf("%w for %s", errStartInterrupted, rootDir)
	}

	if err := quarantineForSpawn(rootDir, discovery); err != nil {
		return Endpoint{}, err
	}
	if err := cleanupOrphanBackend(rootDir); err != nil {
		return Endpoint{}, err
	}

	handedOff = true
	child, err := forkExecChild(rootDir, opts, opts.Port, stopEpoch, lock)
	if err != nil {
		return Endpoint{}, fmt.Errorf("fork child: %w", err)
	}
	defer func() { _ = clearOwnSpawnMarker(rootDir, child.marker) }()
	defer func() {
		if child.handle != nil {
			_ = 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)

View on GitHub (pinned to 71377f2769)