Kuberwastaken/claurst · warning
OAuth callback timeout (5 minutes)
Error message
OAuth callback timeout (5 minutes)
What it means
After binding the callback server, the OAuth flow waits up to 5 minutes (300s) for the browser to complete authorization and hit the local callback URL. `tokio::time::timeout` cancels the accept when the deadline passes and the flow converts that into this error. It means the user never completed the browser authorization in time.
Solutions
- Retry the login and complete the browser authorization promptly (within 5 minutes).
- Open the printed auth URL manually if the browser did not launch automatically.
- Check network/VPN/proxy issues that might block the redirect back to 127.0.0.1.
- If you consistently need longer, the flow must be re-run — the challenge/verifier pair is single-use.
Defensive patterns
Strategy: retry
Try / catch
if let Err(e) = run_oauth_flow(&mut app).await {
if e.to_string().contains("OAuth callback timeout") {
// single-use verifier: restart the whole flow rather than waiting longer
prompt_user("Login timed out; press Enter to retry");
run_oauth_flow(&mut app).await?;
}
} Prevention
- Complete the browser login promptly after the URL opens.
- Open the auth URL manually if auto-launch fails.
- Avoid VPN/captive-portal networks that stall the redirect during login.
When it happens
Trigger: `listener.accept()` wrapped in a 300-second `tokio::time::timeout` elapses inside `wait_for_callback` — no callback connection was received before the deadline.
Common situations: User switched away and never finished browser login; browser failed to open or the auth URL wasn't visited; the authorization page hung on a captive portal/VPN; user took longer than 5 minutes to enter credentials/2FA.
Understand the failure class
Background: Request timed out: what client-side request timeouts mean across libraries (Request timed out, TIMED_OUT, APITimeoutError) — this error's family across 39 libraries.
- Timeouts: ETIMEDOUT, deadlines, and hung requests — what actually expires when a request times out.
Related errors
- Timeout waiting for OAuth callback
- Invalid : contains unsafe characters
- Missing code or state in OAuth callback
- Failed to bind port
- Failed to accept connection
AI-assisted analysis of Kuberwastaken/claurst@b0637c97ec (2026-09-10).
Data as JSON: /api/errors/697fe7ea73e358cf.
Report an issue: GitHub.
Appendix: source
Thrown at src-rust/crates/cli/src/codex_oauth_flow.rs:120
let tokens = exchange_code_for_tokens(&code, &verifier).await?;
// Persist tokens and register an account profile in the registry.
claurst_core::oauth_config::save_codex_tokens_and_register(&tokens, label)?;
eprintln!("Codex login successful!");
Ok(tokens)
}
/// Wait for OAuth callback on local server, extract code and state.
async fn wait_for_callback(listener: TcpListener) -> anyhow::Result<(String, String)> {
use tokio::io::AsyncWriteExt;
let (mut socket, _) = tokio::time::timeout(
std::time::Duration::from_secs(300), // 5 minute timeout
listener.accept(),
)
.await
.map_err(|_| anyhow!("OAuth callback timeout (5 minutes)"))?
.map_err(|e| anyhow!("Failed to accept connection: {}", e))?;
let mut reader = BufReader::new(&mut socket);
let mut request_line = String::new();
reader.read_line(&mut request_line).await?;
// Parse "GET /auth/callback?code=...&state=... HTTP/1.1"
let parts: Vec<&str> = request_line.split_whitespace().collect();
if parts.len() < 2 {
bail!("Invalid HTTP request");
}
let path = parts[1];
let query_start = path.find('?').ok_or_else(|| anyhow!("No query string in callback"))?;
let query = &path[query_start + 1..];
let mut code = String::new();
let mut state = String::new();
View on GitHub (pinned to b0637c97ec)