gastownhall/beads · error
listen on %s: %w
Error message
listen on %s: %w
What it means
This error wraps the failure of net.Listen("tcp", "127.0.0.1:<port>") when the dbproxy tries to open its data listener during ListenAndServe. The library throws it whenever the OS refuses to bind the chosen loopback TCP port, and the underlying OS error is preserved via %w so callers can inspect it with errors.Is.
Source
Thrown at internal/storage/dbproxy/proxy/server.go:227
defer stopEpochWatch()
// abortInterruptedStart tears down a doomed start whose stop epoch
// advanced mid-boot. The interrupting stop is polling proxy.lock under a
// budget (shutdownConfirmDeadline) far smaller than a backend stop can
// take, and nothing has been published, so release the lock BEFORE the
// backend teardown instead of starving the stopper into its timeout.
abortInterruptedStart := func() error {
p.stats.IncBackendStop()
releaseLock()
_ = stopBackendBounded(p.server)
return fmt.Errorf("%w for %s: stop epoch advanced during startup", errStartInterrupted, p.rootDir)
}
addr := fmt.Sprintf("127.0.0.1:%d", p.port)
ln, err := net.Listen("tcp", addr)
if err != nil {
return fmt.Errorf("listen on %s: %w", addr, err)
}
p.listener = ln
defer func() { _ = ln.Close() }()
p.stats.IncListenAndServe()
dataPort, ok := ln.Addr().(*net.TCPAddr)
if !ok {
return fmt.Errorf("proxy: unexpected data listener address %T", ln.Addr())
}
if _, err := identity.WriteSecret(p.rootDir); err != nil {
return fmt.Errorf("write proxy secret: %w", err)
}
var identMu sync.RWMutex
identReply := identity.IdentReply{
Schema: pidfile.SchemaV2,
Role: pidfile.KindProxy,View on GitHub (pinned to 71377f2769)
Solutions
- Check what holds the port with `lsof -i :<port>` or `ss -ltnp` and kill the stale proxy process
- Reconfigure p.port to a free port, or confirm the ephemeral/dynamic port selection is enabled
- Verify loopback networking is available (container/netns restrictions)
- Check firewall/SELinux logs if bind is denied by policy
Example fix
// before p := proxy.New(proxy.WithPort(8080)) // fixed port, already in use // after p := proxy.New(proxy.WithPort(0)) // let the OS pick a free loopback port
Defensive patterns
Strategy: validation
Validate before calling
// Before starting, check the port is bindable
ln, err := net.Listen("tcp", "127.0.0.1:0")
if err != nil { log.Fatalf("no loopback networking: %v", err) }
_ = ln.Close()
// or verify the specific port is free:
if c, err := net.DialTimeout("tcp", "127.0.0.1:8080", time.Second); err == nil { c.Close(); log.Fatal("port 8080 in use") } Try / catch
err := p.ListenAndServe(ctx)
var opErr *net.OpError
if errors.As(err, &opErr) && errors.Is(opErr.Err, syscall.EADDRINUSE) {
log.Printf("proxy port in use, pick another port")
} Prevention
- Prefer port 0 (OS-assigned) over fixed ports for proxies
- Run at most one proxy per rootDir and guard against orphaned processes
- Smoke-test loopback bind in restricted environments (containers, CI) before startup
When it happens
Trigger: ListenAndServe is called and net.Listen on the proxy's configured port fails - typically because the port is already bound by another process, the port is 0-but-restricted, or the loopback interface is unavailable.
Common situations: A stale proxy process still holds the port (orphaned after a crash); two proxies configured with the same port; running in a sandbox/container without loopback networking; port falls in a blocked ephemeral range; SELinux/AppArmor denying bind.
Related errors
- allocating ephemeral port: %w
- ensureProxiedServerConfig: pick free port: %w
- bind %s: %w
- identity: dial control listener: %w
- identity: write request: %w
AI-assisted analysis of gastownhall/beads@71377f2769 (2026-08-30).
Data as JSON: /api/errors/f47a689b7f2fcc7c.
Report an issue: GitHub.