{"record":{"id":"9a3ae0d19188fdc8","repo":"router-for-me/CLIProxyAPI","slug":"server-failed-to-start-w","errorCode":null,"errorMessage":"server failed to start: %w","messagePattern":"server failed to start: %w","errorType":"console","errorClass":null,"httpStatus":null,"severity":"error","filePath":"internal/auth/claude/oauth_server.go","lineNumber":101,"sourceCode":"\t}\n\n\tmux := http.NewServeMux()\n\tmux.HandleFunc(\"/callback\", s.handleCallback)\n\tmux.HandleFunc(\"/success\", s.handleSuccess)\n\n\ts.server = &http.Server{\n\t\tAddr:         fmt.Sprintf(\":%d\", s.port),\n\t\tHandler:      mux,\n\t\tReadTimeout:  10 * time.Second,\n\t\tWriteTimeout: 10 * time.Second,\n\t}\n\n\ts.running = true\n\n\t// Start server in goroutine\n\tgo func() {\n\t\tif err := s.server.ListenAndServe(); err != nil && !errors.Is(err, http.ErrServerClosed) {\n\t\t\ts.errorChan <- fmt.Errorf(\"server failed to start: %w\", err)\n\t\t}\n\t}()\n\n\t// Give server a moment to start\n\ttime.Sleep(100 * time.Millisecond)\n\n\treturn nil\n}\n\n// Stop gracefully stops the OAuth callback server.\n// It performs a graceful shutdown of the HTTP server with a timeout.\n//\n// Parameters:\n//   - ctx: The context for controlling the shutdown process\n//\n// Returns:\n//   - error: An error if the server fails to stop gracefully\nfunc (s *OAuthServer) Stop(ctx context.Context) error {","sourceCodeStart":83,"sourceCodeEnd":119,"githubUrl":"https://github.com/router-for-me/CLIProxyAPI/blob/78f0c4079e3e6273d65d03b5549cffc898703264/internal/auth/claude/oauth_server.go#L83-L119","documentation":"Start() launches ListenAndServe in a goroutine and forwards any non-ErrServerClosed failure into errorChan, which WaitForCallback picks up. This means the server can pass the port pre-check but still fail asynchronously moments later (a race where another process grabs the port, or a bind permission error). The error reaches the caller as a wrapped listen failure during WaitForCallback, not from Start itself.","triggerScenarios":"Another process binds the port between the isPortAvailable check and ListenAndServe; binding fails for OS-level reasons (permission on privileged port, FD limits); the goroutine's ListenAndServe returns immediately with EADDRINUSE.","commonSituations":"High-churn environments where many processes race for ephemeral callback ports; running inside containers with restricted network capabilities; concurrent logins from multiple CI jobs on the same host.","solutions":["Read the wrapped error text — EADDRINUSE means a port race: retry login, ideally with a fresh --oauth-callback-port.","For permission/FD errors, run on an unprivileged port (>1024) and check ulimit -n.","Serialize login flows so two processes never race for the same callback port.","If it persists, reproduce with `curl http://127.0.0.1:<port>` during login to see what actually answered."],"exampleFix":null,"handlingStrategy":"try-catch","validationCode":null,"typeGuard":null,"tryCatchPattern":"go func() {\n    if err := server.Start(); err != nil { log.Fatal(err) }\n}()\nresult, err := server.WaitForCallback(timeout)\nif err != nil && strings.Contains(err.Error(), \"server failed to start\") {\n    // async bind failure: port raced — retry with a new port\n}","preventionTips":["Always consume WaitForCallback's error channel; the real listen error surfaces there, not from Start.","Pick random high ports per attempt to avoid bind races."],"tags":["claude","oauth","port","server","concurrency"],"backgroundTag":null,"analyzedSha":"78f0c4079e3e6273d65d03b5549cffc898703264","analyzedAt":"2026-08-15T12:26:37.444Z","schemaVersion":2},"datasetVersion":"2026-08-15T17:31:12.345Z"}