gastownhall/beads · error

ensureProxiedServerConfig: pick free port: %w

Error message

ensureProxiedServerConfig: pick free port: %w

What it means

When generating a fresh proxied-server config, bd must allocate a free TCP port for the proxy to listen on; this wraps a failure from proxy.PickFreePort. PickFreePort typically binds a listener on 127.0.0.1:0 and reads back the assigned port, so this error means the OS refused to allocate any ephemeral port at pick time.

Source

Thrown at cmd/bd/proxied_server.go:150

		}
		return path, nil
	}

	root := filepath.Dir(path)
	if err := os.MkdirAll(root, config.BeadsDirPerm); err != nil {
		return "", fmt.Errorf("ensureProxiedServerConfig: mkdir %s: %w", root, err)
	}

	switch _, err := os.Stat(path); {
	case err == nil:
		return path, nil
	case !os.IsNotExist(err):
		return "", fmt.Errorf("ensureProxiedServerConfig: stat %s: %w", path, err)
	}

	port, err := proxy.PickFreePort()
	if err != nil {
		return "", fmt.Errorf("ensureProxiedServerConfig: pick free port: %w", err)
	}

	body, err := renderProxiedServerConfig(port)
	if err != nil {
		return "", fmt.Errorf("ensureProxiedServerConfig: render YAML: %w", err)
	}
	if err := atomicWriteFile(resolveConfigWriteTarget(path), body); err != nil {
		return "", fmt.Errorf("ensureProxiedServerConfig: write %s: %w", path, err)
	}
	return path, nil
}

// resolveConfigWriteTarget resolves path to its physical location before
// an atomic rewrite. os.Rename's destination argument does not follow
// symlinks — it unlinks and replaces whatever is AT that path, symlink or
// not — so writing straight to a symlinked config.yaml would silently
// replace the symlink itself with a regular file instead of updating the
// file it points at. Falls back to path unresolved when it does not exist

View on GitHub (pinned to 71377f2769)

Solutions

  1. Check port exhaustion: `ss -s` (count of tcp sockets / TIME_WAIT) and widen or tune `net.ipv4.ip_local_port_range` via sysctl, or enable `net.ipv4.tcp_tw_reuse`.
  2. Find and stop the process leaking sockets (`ss -tanp | awk '{print $6}' | sort | uniq -c`, then fix/kill the offender).
  3. If inside a container/sandbox, confirm the seccomp/AppArmor/sandbox profile permits bind() on loopback; relax the profile or run the daemon on the host network namespace.
  4. Retry after load subsides — the failure can be transient under heavy connection churn.
  5. Restart the host or network namespace as a blunt reset when thousands of orphaned sockets cannot be reclaimed.

Example fix

// before (host exhausted ephemeral ports)
sysctl net.ipv4.ip_local_port_range="32768 32799"   # too narrow
// after
sudo sysctl -w net.ipv4.ip_local_port_range="1024 65535"
Defensive patterns

Strategy: retry

Validate before calling

func canBindLoopback() error {
	l, err := net.Listen("tcp", "127.0.0.1:0")
	if err != nil {
		return err
	}
	l.Close()
	return nil
}

Try / catch

port, err := proxy.PickFreePort()
if err != nil {
	if isEphemeralPortExhaustion(err) { // check ss -s / TIME_WAIT count
		time.Sleep(2 * time.Second)
		port, err = proxy.PickFreePort()
	}
	if err != nil {
		return fmt.Errorf("no free port available: %w", err)
	}
}

Prevention

When it happens

Trigger: proxy.PickFreePort() fails: no available ephemeral ports (net.ipv4.ip_local_port_range exhausted, thousands of TIME_WAIT sockets), a security/seccomp or container policy forbidding bind(), or listen() on the wildcard/loopback blocked inside the sandbox.

Common situations: Long-lived CI machines leaking sockets until ephemeral ports run out; strict Docker/Kubernetes seccomp or AppArmor profiles; `net.ipv4.ip_local_port_range` misconfigured too narrow; proxies/VPN software exhausting ports; test suites opening thousands of parallel listeners.

Related errors


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