router-for-me/CLIProxyAPI · error
server failed to start: %w
Error message
server failed to start: %w
What it means
Start() launches ListenAndServe in a goroutine and forwards any non-ErrServerClosed failure into errorChan, which WaitForCallback picks up. This means the server can pass the port pre-check but still fail asynchronously moments later (a race where another process grabs the port, or a bind permission error). The error reaches the caller as a wrapped listen failure during WaitForCallback, not from Start itself.
Source
Thrown at internal/auth/claude/oauth_server.go:101
}
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) {
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 text — EADDRINUSE means a port race: retry login, ideally with a fresh --oauth-callback-port.
- For permission/FD errors, run on an unprivileged port (>1024) and check ulimit -n.
- Serialize login flows so two processes never race for the same callback port.
- If it persists, reproduce with `curl http://127.0.0.1:<port>` during login to see what actually answered.
Defensive patterns
Strategy: try-catch
Try / catch
go func() {
if err := server.Start(); err != nil { log.Fatal(err) }
}()
result, err := server.WaitForCallback(timeout)
if err != nil && strings.Contains(err.Error(), "server failed to start") {
// async bind failure: port raced — retry with a new port
} Prevention
- Always consume WaitForCallback's error channel; the real listen error surfaces there, not from Start.
- Pick random high ports per attempt to avoid bind races.
When it happens
Trigger: Another process binds the port between the isPortAvailable check and ListenAndServe; binding fails for OS-level reasons (permission on privileged port, FD limits); the goroutine's ListenAndServe returns immediately with EADDRINUSE.
Common situations: High-churn environments where many processes race for ephemeral callback ports; running inside containers with restricted network capabilities; concurrent logins from multiple CI jobs on the same host.
Related errors
- port_in_use
- server failed to start: %w
- port %d is already in use
- fetch Claude OAuth %s: HTTP client is nil
- fetch Claude OAuth %s: access token is empty
AI-assisted analysis of router-for-me/CLIProxyAPI@78f0c4079e (2026-08-15).
Data as JSON: /api/errors/9a3ae0d19188fdc8.
Report an issue: GitHub.