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

  1. Find and stop the holder: `lsof -i :<port>` (or `ss -ltnp | grep <port>`) and kill the stale process.
  2. Wait for or terminate the other concurrent login attempt before retrying.
  3. Run the login flow on a different callback port (the CLI exposes --oauth-callback-port).
  4. 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

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


AI-assisted analysis of router-for-me/CLIProxyAPI@78f0c4079e (2026-08-15). Data as JSON: /api/errors/d22107a5d19e089d. Report an issue: GitHub.