router-for-me/CLIProxyAPI · error · AuthenticationError
port_in_use
port_in_use
Error message
port %d is already in use
What it means
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.
Source
Thrown at internal/auth/claude/oauth_server.go:82
}
// Start starts the OAuth callback server.
// It sets up the HTTP handlers for the callback and success endpoints,
// and begins listening on the specified port.
//
// Returns:
// - error: An error if the server fails to start
func (s *OAuthServer) Start() error {
s.mu.Lock()
defer s.mu.Unlock()
if s.running {
return fmt.Errorf("server is already running")
}
// Check if port is available
if !s.isPortAvailable() {
return fmt.Errorf("port %d is already in use", s.port)
}
mux := http.NewServeMux()
mux.HandleFunc("/callback", s.handleCallback)
mux.HandleFunc("/success", s.handleSuccess)
s.server = &http.Server{
Addr: fmt.Sprintf(":%d", s.port),
Handler: mux,
ReadTimeout: 10 * time.Second,
WriteTimeout: 10 * time.Second,
}
s.running = true
// Start server in goroutine
go func() {
if err := s.server.ListenAndServe(); err != nil && !errors.Is(err, http.ErrServerClosed) {View on GitHub (pinned to 78f0c4079e)
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.
Example fix
# before ./cli-proxy-api login # default callback port already bound # after ./cli-proxy-api login --oauth-callback-port 53100
Defensive patterns
Strategy: validation
Validate before calling
ln, err := net.Listen("tcp", fmt.Sprintf(":%d", port))
if err != nil {
return fmt.Errorf("choose another --oauth-callback-port: %d busy", port)
}
ln.Close()
err = oauthServer.Start() Try / catch
if err := server.Start(); err != nil && strings.Contains(err.Error(), "already in use") {
// reconfigure port and retry start
} Prevention
- Check the callback port is free before launching login.
- Never run two login flows concurrently on one host without distinct ports.
When it happens
Trigger: 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.
Common situations: 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.
Related errors
- server failed to start: %w
- port %d is already in use
- callback_timeout
- server failed to start: %w
- fetch Claude OAuth %s: HTTP client is nil
AI-assisted analysis of router-for-me/CLIProxyAPI@78f0c4079e (2026-08-15).
Data as JSON: /api/errors/d22107a5d19e089d.
Report an issue: GitHub.