gastownhall/beads · critical
bind %s: %w
Error message
bind %s: %w
What it means
The server binds its TCP listener via net.Listen and wraps any OS-level error with 'bind %s: %w', where %s is the configured address. This surfaces kernel/socket errors: address already in use, permission denied on privileged ports, or an invalid/unavailable address.
Source
Thrown at internal/httpapi/server.go:545
sem: make(chan struct{}, maxInflight),
semTimeout: semAcquireTimeout,
semWarn: saturationWarn,
closing: make(chan struct{}),
maxWatchStreams: maxWatchStreams,
log: log.New(cfg.Stderr, "bd serve: ", log.LstdFlags|log.LUTC),
stdout: cfg.Stdout,
ctxBody: contextResponse(cfg.Workspace, cfg.SchemaVersion, Capabilities()),
hosts: newHostPolicy(ip, cfg.AllowedHosts),
auth: cfg.Auth,
idPrefix: prefix,
maxConns: maxConns,
}
ln, err := net.Listen("tcp", cfg.Addr)
if err != nil {
return nil, fmt.Errorf("bind %s: %w", cfg.Addr, err)
}
s.listener = netutil.LimitListener(ln, s.maxConns)
s.http = &http.Server{
Handler: s.handler(),
ReadHeaderTimeout: readHeaderTimeout,
ReadTimeout: readTimeout,
IdleTimeout: idleTimeout,
MaxHeaderBytes: maxHeaderBytes,
ErrorLog: log.New(cfg.Stderr, "bd serve: http: ", log.LstdFlags|log.LUTC),
ConnState: s.connState,
}
// Tell the streams to wind up as soon as a drain starts. Without it a
// graceful shutdown waits out the whole drain timeout on any open stream and
// then reports itself forced, which is the one shutdown signal an operator
// is meant to be able to trust.
s.http.RegisterOnShutdown(s.closeStreams)
View on GitHub (pinned to 71377f2769)
Solutions
- Check what holds the port (lsof -i :PORT or ss -ltnp) and stop it, or pick a different port
- Use port 0 for an ephemeral port or a port above 1024 if lacking privileges
- Verify the IP in cfg.Addr is actually assigned to this host (ip addr); use 127.0.0.1 for local-only
Example fix
// before Addr: "127.0.0.1:8080" // already in use // after Addr: "127.0.0.1:8081" // or "127.0.0.1:0" for ephemeral
Defensive patterns
Strategy: retry
Validate before calling
// check port availability before start
conn, err := net.Listen("tcp", addr)
if err == nil { conn.Close() } Try / catch
srv, err := httpapi.Listen(cfg)
if err != nil {
var oe *net.OpError
if errors.As(err, &oe) && errors.Is(oe.Err, syscall.EADDRINUSE) {
// pick another port or wait and retry
}
} Prevention
- Use port 0 (ephemeral) in dev/test to avoid conflicts
- Run with a supervisor that waits on port release (SO_REUSEADDR handled by Go)
- Never run unprivileged with ports below 1024
When it happens
Trigger: Calling Listen when another process already holds the port (EADDRINUSE), binding port <1024 without privileges (EACCES), or binding an IP not assigned to the machine (EADDRNOTAVAIL).
Common situations: Two instances of the server started simultaneously; a stale process still holding the port; running as non-root and trying port 80/443; container without the interface IP present.
Related errors
- allocating ephemeral port: %w
- port %d is busy but cannot identify the process.\n\nCheck wi
- port %d is in use by a non-dolt process (PID %d).\n\n%s\n\nF
- port %d is in use by another project's dolt server (PID %d).
- listen on %s: %w
AI-assisted analysis of gastownhall/beads@71377f2769 (2026-08-30).
Data as JSON: /api/errors/ae70ab3cdc081598.
Report an issue: GitHub.