router-for-me/CLIProxyAPI · error

server failed to start: %w

Error message

server failed to start: %w

What it means

The Codex callback server passed its port pre-check, but ListenAndServe — started in a goroutine — failed asynchronously and pushed the wrapped error into errorChan, where WaitForCallback surfaces it. It indicates a bind race or OS-level listen failure that occurred between the check and the actual bind, rather than the straightforward port-already-bound case.

Source

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

	}

	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) {
			s.errorChan <- fmt.Errorf("server failed to start: %w", err)
		}
	}()

	// Give server a moment to start
	time.Sleep(100 * time.Millisecond)

	return nil
}

// Stop gracefully stops the OAuth callback server.
// It performs a graceful shutdown of the HTTP server with a timeout.
//
// Parameters:
//   - ctx: The context for controlling the shutdown process
//
// Returns:
//   - error: An error if the server fails to stop gracefully
func (s *OAuthServer) Stop(ctx context.Context) error {

View on GitHub (pinned to 78f0c4079e)

Solutions

  1. Read the wrapped error: EADDRINUSE → port race, retry with a fresh --oauth-callback-port; EACCES → use an unprivileged port; EMFILE → raise ulimit -n or reduce concurrent logins.
  2. Serialize Codex login flows so ports are not contended.
  3. For containers, ensure standard networking capabilities and sane FD limits.
Defensive patterns

Strategy: try-catch

Try / catch

result, err := server.WaitForCallback(timeout)
if err != nil && strings.Contains(err.Error(), "server failed to start") {
    // async bind failure (EADDRINUSE race / EACCES / EMFILE) — restart on a fresh port
    server = newServerOn(pickFreePort())
    result, err = server.WaitForCallback(timeout)
}

Prevention

When it happens

Trigger: Another process seizes the port in the window between isPortAvailable and ListenAndServe; bind fails on a privileged port without CAP_NET_BIND_SERVICE; FD exhaustion prevents socket creation during heavy concurrent logins.

Common situations: Multiple CI jobs logging in simultaneously on one host racing for the same port; containers dropping capabilities so even high ports occasionally fail; systems at the FD limit.

Related errors


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