{"record":{"id":"ae70ab3cdc081598","repo":"gastownhall/beads","slug":"bind-s-w","errorCode":null,"errorMessage":"bind %s: %w","messagePattern":"bind (.+?): %w","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"critical","filePath":"internal/httpapi/server.go","lineNumber":545,"sourceCode":"\t\tsem:        make(chan struct{}, maxInflight),\n\t\tsemTimeout: semAcquireTimeout,\n\t\tsemWarn:    saturationWarn,\n\n\t\tclosing:         make(chan struct{}),\n\t\tmaxWatchStreams: maxWatchStreams,\n\n\t\tlog:      log.New(cfg.Stderr, \"bd serve: \", log.LstdFlags|log.LUTC),\n\t\tstdout:   cfg.Stdout,\n\t\tctxBody:  contextResponse(cfg.Workspace, cfg.SchemaVersion, Capabilities()),\n\t\thosts:    newHostPolicy(ip, cfg.AllowedHosts),\n\t\tauth:     cfg.Auth,\n\t\tidPrefix: prefix,\n\t\tmaxConns: maxConns,\n\t}\n\n\tln, err := net.Listen(\"tcp\", cfg.Addr)\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"bind %s: %w\", cfg.Addr, err)\n\t}\n\ts.listener = netutil.LimitListener(ln, s.maxConns)\n\n\ts.http = &http.Server{\n\t\tHandler:           s.handler(),\n\t\tReadHeaderTimeout: readHeaderTimeout,\n\t\tReadTimeout:       readTimeout,\n\t\tIdleTimeout:       idleTimeout,\n\t\tMaxHeaderBytes:    maxHeaderBytes,\n\t\tErrorLog:          log.New(cfg.Stderr, \"bd serve: http: \", log.LstdFlags|log.LUTC),\n\t\tConnState:         s.connState,\n\t}\n\t// Tell the streams to wind up as soon as a drain starts. Without it a\n\t// graceful shutdown waits out the whole drain timeout on any open stream and\n\t// then reports itself forced, which is the one shutdown signal an operator\n\t// is meant to be able to trust.\n\ts.http.RegisterOnShutdown(s.closeStreams)\n","sourceCodeStart":527,"sourceCodeEnd":563,"githubUrl":"https://github.com/gastownhall/beads/blob/71377f276968b452ee607177637970a4ff888584/internal/httpapi/server.go#L527-L563","documentation":"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.","triggerScenarios":"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).","commonSituations":"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.","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"],"exampleFix":"// before\nAddr: \"127.0.0.1:8080\"  // already in use\n// after\nAddr: \"127.0.0.1:8081\"  // or \"127.0.0.1:0\" for ephemeral","handlingStrategy":"retry","validationCode":"// check port availability before start\nconn, err := net.Listen(\"tcp\", addr)\nif err == nil { conn.Close() }","typeGuard":null,"tryCatchPattern":"srv, err := httpapi.Listen(cfg)\nif err != nil {\n    var oe *net.OpError\n    if errors.As(err, &oe) && errors.Is(oe.Err, syscall.EADDRINUSE) {\n        // pick another port or wait and retry\n    }\n}","preventionTips":["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"],"tags":["network","bind","port-conflict","os-error"],"backgroundTag":"address-already-in-use","analyzedSha":"71377f276968b452ee607177637970a4ff888584","analyzedAt":"2026-08-30T18:55:39.744Z","schemaVersion":2},"datasetVersion":"2026-08-30T23:17:21.991Z"}