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
- 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.
- Serialize Codex login flows so ports are not contended.
- 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
- Serialize login flows on a shared host to avoid port races.
- Read the wrapped syscall error to pick the right fix (port vs privileges vs FD limits).
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
- server failed to start: %w
- port %d is already in use
- port_in_use
- missing access_token and refresh_token
- refresh response did not include access_token
AI-assisted analysis of router-for-me/CLIProxyAPI@78f0c4079e (2026-08-15).
Data as JSON: /api/errors/c77e1d0fa116ab92.
Report an issue: GitHub.