gastownhall/beads · critical

dolt sql-server exited before listener became ready

Error message

dolt sql-server exited before listener became ready

What it means

waitReady polls the server with TCP dials until the dolt sql-server listener accepts. If the child process exits (its errgroup context becomes done) before a dial succeeds, Start fails with this message. The actual reason the child died is not in this error — check the configured log file or the child's stderr for the real cause.

Solutions

  1. Read the server log file (the logFilePath passed to NewDoltServer) for the child's actual exit reason
  2. Check the configured port: ss -ltnp | grep <port> — kill the conflicting process or change the port in server.yaml
  3. Verify the dolt binary runs: run `<doltBinExec> version` manually
  4. Check the data directory for stale locks or corruption (bd doctor / dolt fsck)
  5. Start again; if this recurs after fixing, inspect the YAML config for invalid runtime options

Example fix

// before
srv, _ := server.NewDoltServer(bin, root, "server.yaml", "", 0, db)
if err := srv.Start(ctx); err != nil { return err } // opaque 'exited before listener became ready'
// after
srv, _ := server.NewDoltServer(bin, root, "server.yaml", filepath.Join(root, "dolt-server.log"), 0, db)
if err := srv.Start(ctx); err != nil {
    log.Fatalf("%v; see %s for the dolt sql-server exit reason", err, logPath)
}
Defensive patterns

Strategy: try-catch

Validate before calling

if _, err := os.Stat(doltBinExec); err != nil { return fmt.Errorf("dolt binary missing: %w", err) }
out, err := exec.Command(doltBinExec, "version").CombinedOutput()
if err != nil { return fmt.Errorf("dolt binary unusable: %w: %s", err, out) }
// port check
if ln, err := net.Listen("tcp", ":"+port); err != nil { return fmt.Errorf("port %s in use", port) } else { ln.Close() }

Try / catch

if err := srv.Start(ctx); err != nil {
    if strings.Contains(err.Error(), "exited before listener became ready") {
        return fmt.Errorf("dolt sql-server failed to start; check server log at %s and port %d: %w", logPath, port, err)
    }
    return err
}

Prevention

When it happens

Trigger: Calling DoltServer.Start when the spawned `dolt sql-server` process exits during the readiness window (30s, polled every 50ms): bad YAML server config, port already bound by another process, dolt binary fails to launch, data directory locked/corrupt, or another waitReady caller's egCtx was cancelled.

Common situations: Config file pointing at a port already used by another dolt/MySQL server; malformed server.yaml accepted by parse but rejected at runtime; missing or wrong-arch dolt binary; two proxies racing on the same rootDir; dolt crashing on startup due to a corrupt or locked .dolt directory.

Related errors


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

Appendix: source

Thrown at internal/storage/dbproxy/server/doltserver.go:323

		defer lock.Unlock()
		return cmd.Wait()
	})

	if err := s.waitReady(ctx); err != nil {
		cancel()
		_ = s.eg.Wait()
		s.eg, s.egCtx, s.cancel, s.pid = nil, nil, nil, 0
		_ = pidfile.Remove(s.rootDir, PIDFileName)
		return fmt.Errorf("server: DoltServer.Start: %w", err)
	}
	return nil
}

func (s *DoltServer) waitReady(ctx context.Context) error {
	deadline := time.Now().Add(startReadyTimeout)
	for {
		if s.egCtx.Err() != nil {
			return errors.New("dolt sql-server exited before listener became ready")
		}

		dctx, dcancel := context.WithTimeout(ctx, startReadyDialTimeout)
		conn, err := s.Dial(dctx)
		dcancel()
		if err == nil {
			_ = conn.Close()
			return nil
		}

		if time.Now().After(deadline) {
			return fmt.Errorf("listener not ready after %s: %w", startReadyTimeout, err)
		}

		select {
		case <-ctx.Done():
			return ctx.Err()
		case <-s.egCtx.Done():

View on GitHub (pinned to 71377f2769)