Kuberwastaken/claurst · error · anyhow::Error
Timeout waiting for OAuth callback
Error message
Timeout waiting for OAuth callback
What it means
wait_for_authorization_code wraps listener.accept() in a 180-second tokio timeout; if no browser connects to the callback within that window the timeout fires and this error is raised. The library gives up rather than blocking forever on a user who never completes the browser authorization.
Solutions
- Re-run the auth flow and complete the browser authorization promptly (within 3 minutes)
- Open the printed authorization URL manually if no browser launched
- Verify the configured redirect URI matches the listener port so the provider redirects to the local server
- In headless environments, forward the callback port (ssh -L) or use a device-code flow instead
Defensive patterns
Strategy: retry
Try / catch
loop {
match run_mcp_auth_flow(url).await {
Ok(s) => break s,
Err(e) if e.to_string().contains("Timeout waiting") => {
eprintln!("No callback within 180s; reopening browser...");
open_browser(url);
}
Err(e) => return Err(e),
}
} Prevention
- Complete the browser authorization promptly after starting the flow
- Run in an environment where the auth URL can be opened (or forward the port)
- Ensure the redirect URI port matches the listener so the browser reaches the callback
When it happens
Trigger: run_mcp_auth_session opened the auth URL but no HTTP request hits the local callback listener within 3 minutes — browser never opened, user closed the tab, or the redirect points at the wrong port/path.
Common situations: User ignores or misses the browser tab; headless/SSH environment with no browser; redirect URI port in config differs from the bound listener so the provider redirects elsewhere; slow 2FA/login flow exceeding 180s.
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
- OAuth callback timeout (5 minutes)
- Invalid : contains unsafe characters
- models endpoint returned
- Invalid JWT: expected at least 2 dot-separated segments
- Bridge register: auth error
AI-assisted analysis of Kuberwastaken/claurst@b0637c97ec (2026-09-10).
Data as JSON: /api/errors/1977cee6c77cdd5a.
Report an issue: GitHub.
Appendix: source
Thrown at src-rust/crates/mcp/src/oauth.rs:246
} else {
redirect_url.path().to_string()
};
let listener = TcpListener::bind((host.as_str(), port))
.await
.map_err(|e| anyhow::anyhow!("Failed to bind OAuth callback listener on {}:{}: {}", host, port, e))?;
Ok((listener, host, callback_path))
}
async fn wait_for_authorization_code(
listener: TcpListener,
host: &str,
callback_path: &str,
expected_state: Option<&str>,
) -> anyhow::Result<String> {
let (mut socket, _) = tokio::time::timeout(Duration::from_secs(180), listener.accept())
.await
.map_err(|_| anyhow::anyhow!("Timeout waiting for OAuth callback"))?
.map_err(|e| anyhow::anyhow!("Failed to accept OAuth callback connection: {}", e))?;
let (reader, mut writer) = socket.split();
let mut reader = BufReader::new(reader);
let mut request_line = String::new();
reader
.read_line(&mut request_line)
.await
.map_err(|e| anyhow::anyhow!("Failed to read OAuth callback request: {}", e))?;
loop {
let mut header = String::new();
reader
.read_line(&mut header)
.await
.map_err(|e| anyhow::anyhow!("Failed to read OAuth callback headers: {}", e))?;
if header.trim().is_empty() {
break;
}View on GitHub (pinned to b0637c97ec)