gastownhall/beads · error

allocating ephemeral port: %w

Error message

allocating ephemeral port: %w

What it means

allocateEphemeralPort asks the kernel for a free TCP port by binding to host:0 via net.Listen, reading the assigned port, and closing the listener. This error wraps any net.Listen failure while obtaining that ephemeral port, so Start() cannot proceed to launch dolt sql-server.

Source

Thrown at internal/doltserver/doltserver.go:388

func pidPath(beadsDir string) string  { return filepath.Join(beadsDir, PIDFileName) }
func logPath(beadsDir string) string  { return filepath.Join(beadsDir, "dolt-server.log") }
func lockPath(beadsDir string) string { return filepath.Join(beadsDir, "dolt-server.lock") }
func portPath(beadsDir string) string { return filepath.Join(beadsDir, PortFileName) }

// MaxDoltServers is the hard ceiling on concurrent dolt sql-server processes.
// Allows up to 3 (e.g., multiple projects).
func maxDoltServers() int {
	return 3
}

// allocateEphemeralPort asks the OS for a free TCP port on host.
// It binds to port 0, reads the assigned port, and closes the listener.
// The caller should pass the returned port to dolt sql-server promptly
// to minimize the TOCTOU window.
func allocateEphemeralPort(host string) (int, error) {
	ln, err := net.Listen("tcp", net.JoinHostPort(host, "0"))
	if err != nil {
		return 0, fmt.Errorf("allocating ephemeral port: %w", err)
	}
	port := ln.Addr().(*net.TCPAddr).Port
	_ = ln.Close()
	return port, nil
}

// isPortAvailable checks if a TCP port is available for binding.
func isPortAvailable(host string, port int) bool {
	addr := net.JoinHostPort(host, strconv.Itoa(port))
	ln, err := net.Listen("tcp", addr)
	if err != nil {
		return false
	}
	_ = ln.Close()
	return true
}

// reclaimPort ensures an explicit (user-configured) port is available for use.

View on GitHub (pinned to 71377f2769)

Solutions

  1. Read the wrapped net error (e.g. 'lookup host: no such host', 'cannot assign requested address')
  2. Verify the configured host is a local interface or empty string for all interfaces
  3. Use 127.0.0.1 or 'localhost' instead of an unresolvable/remote hostname
  4. If using IPv6, confirm the host supports it (ip -6 addr) or switch to IPv4
  5. Check firewall/SELinux rules that may block TCP binds

Example fix

// before: host set to a remote machine
export BEADS_DOLT_SERVER_HOST=db.internal.example.com
// after: bind locally; point clients at the remote host explicitly
export BEADS_DOLT_SERVER_HOST=127.0.0.1
Defensive patterns

Strategy: retry

Validate before calling

// Pre-check that the host is bindable locally
host := os.Getenv("BEADS_DOLT_SERVER_HOST")
if host != "" && net.ParseIP(host) != nil {
    addrs, _ := net.InterfaceAddrs()
    found := false
    for _, a := range addrs {
        if strings.HasPrefix(a.String(), host+"/") { found = true }
    }
    if !found && host != "127.0.0.1" && host != "::1" {
        // host is not a local interface — bind will likely fail
    }
}

Try / catch

port, err := doltserver.Start(beadsDir)
if err != nil && strings.Contains(err.Error(), "allocating ephemeral port") {
    // retry once, or fall back to default host
    os.Unsetenv("BEADS_DOLT_SERVER_HOST")
    port, err = doltserver.Start(beadsDir)
}

Prevention

When it happens

Trigger: Start() calling allocateEphemeralPort(host) where net.Listen("tcp", host:0) fails — host resolves to an address with no local interface, IPv6 host on a machine without IPv6 support, or restrictive firewall/SELinux policy blocking socket bind.

Common situations: BEADS_DOLT_SERVER_HOST configured to a hostname that doesn't resolve locally or to a remote host, hosts with IPv6 disabled receiving an IPv6 bind address, sandboxed CI runners denying socket creation.

Related errors


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