{"record":{"id":"a8100c2a5593f178","repo":"router-for-me/CLIProxyAPI","slug":"timeout-waiting-for-oauth-callback","errorCode":null,"errorMessage":"timeout waiting for OAuth callback","messagePattern":"timeout waiting for OAuth callback","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"internal/auth/codex/oauth_server.go","lineNumber":154,"sourceCode":"\n// WaitForCallback waits for the OAuth callback with a timeout.\n// It blocks until either an OAuth result is received, an error occurs,\n// or the specified timeout is reached.\n//\n// Parameters:\n//   - timeout: The maximum time to wait for the callback\n//\n// Returns:\n//   - *OAuthResult: The OAuth result if successful\n//   - error: An error if the callback times out or an error occurs\nfunc (s *OAuthServer) WaitForCallback(timeout time.Duration) (*OAuthResult, error) {\n\tselect {\n\tcase result := <-s.resultChan:\n\t\treturn result, nil\n\tcase err := <-s.errorChan:\n\t\treturn nil, err\n\tcase <-time.After(timeout):\n\t\treturn nil, fmt.Errorf(\"timeout waiting for OAuth callback\")\n\t}\n}\n\n// handleCallback handles the OAuth callback endpoint.\n// It extracts the authorization code and state from the callback URL,\n// validates the parameters, and sends the result to the waiting channel.\n//\n// Parameters:\n//   - w: The HTTP response writer\n//   - r: The HTTP request\nfunc (s *OAuthServer) handleCallback(w http.ResponseWriter, r *http.Request) {\n\tlog.Debug(\"Received OAuth callback\")\n\n\t// Validate request method\n\tif r.Method != http.MethodGet {\n\t\thttp.Error(w, \"Method not allowed\", http.StatusMethodNotAllowed)\n\t\treturn\n\t}","sourceCodeStart":136,"sourceCodeEnd":172,"githubUrl":"https://github.com/router-for-me/CLIProxyAPI/blob/78f0c4079e3e6273d65d03b5549cffc898703264/internal/auth/codex/oauth_server.go#L136-L172","documentation":"Thrown by WaitForCallback when the local OAuth callback HTTP server does not receive the provider redirect within the caller-supplied timeout. The function blocks on a select over the result channel, the error channel, and time.After(timeout); if the browser never hits the callback URL before the deadline, the timeout branch wins. It means the authorization leg of the flow (user login + redirect) never completed, not that the token exchange failed.","triggerScenarios":"User never opens or completes the login page; browser redirects to a different port than the one the OAuthServer listens on (--oauth-callback-port mismatch); the callback port is occupied by another process so the real server never gets the request; headless/SSH environment with no browser to reach 127.0.0.1; firewall or browser extension blocks the localhost redirect; timeout passed by the caller is too short for slow interactive login.","commonSituations":"Running cli-proxy-api login inside a container or over SSH without port forwarding; two login attempts racing for the same callback port; corporate proxy stripping the redirect; user walks away from the browser past the timeout (commonly 2-5 minutes).","solutions":["Re-run the login flow and complete the browser prompt promptly, keeping the callback port reachable.","Verify the callback port is free and matches what the flow registered: `lsof -i :<callback-port>` and re-run with `--oauth-callback-port <same-port>`.","On SSH/containers, forward the callback port (e.g. `ssh -L 1455:127.0.0.1:1455`) or run the login on a machine with a browser.","Disable proxies for 127.0.0.1/localhost (NO_PROXY) so the browser redirect is not swallowed.","Increase the timeout value passed to WaitForCallback if the environment is known to be slow."],"exampleFix":"// before\nresult, err := server.WaitForCallback(2 * time.Minute)\n\n// after\nresult, err := server.WaitForCallback(5 * time.Minute)\nif err != nil {\n    log.Errorf(\"oauth callback not received: %v; check the callback port is reachable from your browser\", err)\n}","handlingStrategy":"retry","validationCode":"// Before starting the flow, confirm the callback port is actually free\nln, err := net.Listen(\"tcp\", \":1455\")\nif err != nil {\n    log.Fatalf(\"callback port busy: %v\", err)\n}\n_ = ln.Close() // OAuthServer will re-bind it","typeGuard":null,"tryCatchPattern":"result, err := server.WaitForCallback(5 * time.Minute)\nif err != nil {\n    if strings.Contains(err.Error(), \"timeout waiting for OAuth callback\") {\n        // restart the whole flow: shutdown server, regenerate PKCE, re-open browser\n        log.Warnf(\"login timed out; restarting OAuth flow\")\n        continue // next loop iteration\n    }\n    return err\n}","preventionTips":["Always run the login command in an environment where a browser can reach 127.0.0.1 on the callback port.","Forward the callback port when working over SSH.","Complete the browser prompt promptly; do not leave the flow half-finished.","Ensure only one login flow owns the callback port at a time."],"tags":["oauth","codex","timeout","callback","auth-flow"],"backgroundTag":null,"analyzedSha":"78f0c4079e3e6273d65d03b5549cffc898703264","analyzedAt":"2026-08-15T12:26:37.444Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}