{"record":{"id":"d22107a5d19e089d","repo":"router-for-me/CLIProxyAPI","slug":"port-in-use","errorCode":"port_in_use","errorMessage":"port %d is already in use","messagePattern":"port (.+?) is already in use","errorType":"console","errorClass":"AuthenticationError","httpStatus":null,"severity":"error","filePath":"internal/auth/claude/oauth_server.go","lineNumber":82,"sourceCode":"}\n\n// Start starts the OAuth callback server.\n// It sets up the HTTP handlers for the callback and success endpoints,\n// and begins listening on the specified port.\n//\n// Returns:\n//   - error: An error if the server fails to start\nfunc (s *OAuthServer) Start() error {\n\ts.mu.Lock()\n\tdefer s.mu.Unlock()\n\n\tif s.running {\n\t\treturn fmt.Errorf(\"server is already running\")\n\t}\n\n\t// Check if port is available\n\tif !s.isPortAvailable() {\n\t\treturn fmt.Errorf(\"port %d is already in use\", s.port)\n\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) {","sourceCodeStart":64,"sourceCodeEnd":100,"githubUrl":"https://github.com/router-for-me/CLIProxyAPI/blob/78f0c4079e3e6273d65d03b5549cffc898703264/internal/auth/claude/oauth_server.go#L64-L100","documentation":"The Claude OAuth callback server refuses to start because its pre-check found the configured callback port already bound. The server binds during the interactive login flow to receive the OAuth redirect, and two listeners cannot share the port. A companion code=port_in_use marker is attached for programmatic matching.","triggerScenarios":"Running `Login()` / the Claude OAuth flow while another instance of the proxy (or a previous crashed run) still holds the callback port; any unrelated process occupying the same port; running login in parallel from two shells.","commonSituations":"A previous CLIProxyAPI process did not exit and still holds the port; a second terminal running login concurrently; the port also used by a dev server; CI environments where leftover containers keep ports bound.","solutions":["Find and stop the holder: `lsof -i :<port>` (or `ss -ltnp | grep <port>`) and kill the stale process.","Wait for or terminate the other concurrent login attempt before retrying.","Run the login flow on a different callback port (the CLI exposes --oauth-callback-port).","If it is a legitimate long-lived service on that port, permanently move the OAuth callback port in config."],"exampleFix":"# before\n./cli-proxy-api login  # default callback port already bound\n\n# after\n./cli-proxy-api login --oauth-callback-port 53100","handlingStrategy":"validation","validationCode":"ln, err := net.Listen(\"tcp\", fmt.Sprintf(\":%d\", port))\nif err != nil {\n    return fmt.Errorf(\"choose another --oauth-callback-port: %d busy\", port)\n}\nln.Close()\nerr = oauthServer.Start()","typeGuard":null,"tryCatchPattern":"if err := server.Start(); err != nil && strings.Contains(err.Error(), \"already in use\") {\n    // reconfigure port and retry start\n}","preventionTips":["Check the callback port is free before launching login.","Never run two login flows concurrently on one host without distinct ports."],"tags":["claude","oauth","port","server","auth-flow"],"backgroundTag":null,"analyzedSha":"78f0c4079e3e6273d65d03b5549cffc898703264","analyzedAt":"2026-08-15T12:26:37.444Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}