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.

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)

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.