router-for-me/CLIProxyAPI · error

port is already in use

Error message

port %d is already in use

What it means

The Codex OAuth callback server (mirrors the Claude one) refuses to start because its configured callback port is already bound. The pre-start isPortAvailable probe failed, so the login flow aborts before opening the browser. Distinct from the async failure (error 199), which happens after the probe passes.

Solutions

  1. Identify and stop the holder: `lsof -i :<port>` / `ss -ltnp`, kill the stale process.
  2. Avoid concurrent logins; wait for the first to finish.
  3. Retry login with a different --oauth-callback-port.
  4. Move the callback port away from ports your other services use.

Example fix

# before
./cli-proxy-api login --provider codex   # port held by stale process

# after
kill $(lsof -t -i :1455)
./cli-proxy-api login --provider codex
Defensive patterns

Strategy: validation

Validate before calling

ln, err := net.Listen("tcp", fmt.Sprintf(":%d", port))
if err != nil { return fmt.Errorf("codex callback port %d busy; pass --oauth-callback-port", port) }
ln.Close()

Try / catch

if err := server.Start(); err != nil && strings.Contains(err.Error(), "already in use") {
    port = pickFreePort(); rebuildServer(port)
}

Prevention

When it happens

Trigger: Running Codex login while another CLIProxyAPI instance, a previous crashed process, or an unrelated service holds the callback port; two concurrent logins sharing the default port.

Common situations: Stale background process from an earlier login still bound; parallel logins from two terminals; the callback port colliding with a commonly used dev port; leftover container holding the port in CI.

Related errors


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

Appendix: source

Thrown at internal/auth/codex/oauth_server.go:79

}

// 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("/auth/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)