{"record":{"id":"64087ec862dc3c50","repo":"github/github-mcp-server","slug":"starting-callback-listener-on-s-w","errorCode":null,"errorMessage":"starting callback listener on %s: %w","messagePattern":"starting callback listener on (.+?): %w","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"internal/oauth/callback.go","lineNumber":53,"sourceCode":"}\n\n// listenCallback binds the local callback listener.\n//\n// It binds to loopback (127.0.0.1) by default so the callback server is never\n// exposed on other interfaces. bindAll is set only inside a container, where\n// Docker's published-port DNAT delivers traffic to the container's eth0 rather\n// than to loopback; host-side exposure is still constrained by the publish\n// (e.g. -p 127.0.0.1:8085:8085). A native run — even with a fixed port — stays\n// on loopback.\nfunc listenCallback(port int, bindAll bool) (net.Listener, error) {\n\thost := \"127.0.0.1\"\n\tif bindAll {\n\t\thost = \"0.0.0.0\"\n\t}\n\taddr := fmt.Sprintf(\"%s:%d\", host, port)\n\tlistener, err := net.Listen(\"tcp\", addr)\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"starting callback listener on %s: %w\", addr, err)\n\t}\n\treturn listener, nil\n}\n\n// newCallbackServer starts a callback server on listener that validates state\n// and reports the result on a buffered channel. The redirect URI always uses\n// localhost so it matches the value registered on the OAuth/GitHub App.\nfunc newCallbackServer(listener net.Listener, expectedState string) *callbackServer {\n\tcs := &callbackServer{\n\t\tserver:   &http.Server{ReadHeaderTimeout: 10 * time.Second}, // ReadHeaderTimeout guards against Slowloris.\n\t\tlistener: listener,\n\t\tredirect: fmt.Sprintf(\"http://localhost:%d/callback\", listener.Addr().(*net.TCPAddr).Port),\n\t\tresults:  make(chan callbackResult, 1),\n\t}\n\tcs.server.Handler = cs.handler(expectedState)\n\n\tgo func() {\n\t\tif err := cs.server.Serve(listener); err != nil && err != http.ErrServerClosed {","sourceCodeStart":35,"sourceCodeEnd":71,"githubUrl":"https://github.com/github/github-mcp-server/blob/0ea1f775a7c73eff1bd2e25904d01136756bbfe2/internal/oauth/callback.go#L35-L71","documentation":"net.Listen('tcp', addr) failed for the local OAuth callback listener — the TCP port is already bound (or cannot be bound) on the chosen interface. addr is '127.0.0.1:PORT' for native runs or '0.0.0.0:PORT' inside Docker (listenCallback at internal/oauth/callback.go:44-55). The %w wrap keeps the OS error: 'address already in use' or 'permission denied'.","triggerScenarios":"Another process holds the configured CallbackPort (a previous server instance that did not exit, a second copy of the MCP server); port <1024 requested on Linux without CAP_NET_BIND_SERVICE ('permission denied'); inside Docker, another container in the same network namespace already bound 0.0.0.0:PORT.","commonSituations":"Two MCP server instances configured with the same --oauth-callback-port; a stale process from a crashed session; a dev server coincidentally on the same port; running with a port that requires root; the port registered on the OAuth app being taken by another tool on a shared workstation.","solutions":["Find and stop the holder: lsof -nP -iTCP:PORT -sTCP:LISTEN (macOS/Linux) or ss -ltnp 'sport = :PORT' (Linux)","Set a different --oauth-callback-port (and update the callback URL registered on the OAuth/GitHub App)","If the port must be <1024, run with CAP_NET_BIND_SERVICE or pick an unprivileged port","Kill orphaned instances of the server before starting a new one"],"exampleFix":"// before: second instance with same fixed port\n$ github-mcp-server --oauth-callback-port 8085\n// error: starting callback listener on 127.0.0.1:8085: listen tcp 127.0.0.1:8085: bind: address already in use\n\n// after: free the port or pick another\n$ lsof -tiTCP:8085 -sTCP:LISTEN | xargs kill\n$ github-mcp-server --oauth-callback-port 8086","handlingStrategy":"validation","validationCode":"func portFree(host string, port int) bool {\n    ln, err := net.Listen(\"tcp\", fmt.Sprintf(\"%s:%d\", host, port))\n    if err != nil { return false }\n    _ = ln.Close()\n    return true\n}\n\n// before starting the flow:\nif cfg.CallbackPort != 0 && !portFree(\"127.0.0.1\", cfg.CallbackPort) {\n    return fmt.Errorf(\"callback port %d busy\", cfg.CallbackPort)\n}","typeGuard":null,"tryCatchPattern":"if strings.Contains(err.Error(), \"address already in use\") {\n    // holder conflict: identify via lsof/ss, then stop it or change the port\n}","preventionTips":["Pre-bind-check the fixed callback port at startup","Ensure process managers fully stop old instances (KillMode=control-group for systemd) before starting new ones","Choose an unprivileged, rarely-used port and document it as reserved for this server"],"tags":["network","port","configuration","startup","oauth"],"backgroundTag":null,"analyzedSha":"0ea1f775a7c73eff1bd2e25904d01136756bbfe2","analyzedAt":"2026-08-15T18:10:19.804Z","schemaVersion":2},"datasetVersion":"2026-08-16T03:17:38.424Z"}