Kuberwastaken/claurst · warning · anyhow::Error

Failed to read OAuth callback request

Error message

Failed to read OAuth callback request: {}

What it means

After accepting the browser's callback connection, reading the HTTP request line from the socket failed at the I/O level (connection reset, timeout, or broken socket before a full line arrived). The browser connected but the request could not be read.

Solutions

  1. Retry the auth flow — a single aborted probe connection shouldn't prevent the real browser request
  2. Register an http:// (not https://) redirect URI with the OAuth provider so the browser speaks plain HTTP
  3. Exclude the callback port from port-scanning security agents
  4. Check proxies/middleware that might reset loopback connections

Example fix

// before
"redirect_uris": ["https://127.0.0.1:8090/callback"]
// after
"redirect_uris": ["http://127.0.0.1:8090/callback"]
Defensive patterns

Strategy: retry

Try / catch

match run_mcp_auth_flow(server).await {
    Err(e) if e.to_string().contains("read OAuth callback request") => {
        eprintln!("callback read interrupted; retrying");
        run_mcp_auth_flow(server).await
    }
    other => other,
}

Prevention

When it happens

Trigger: wait_for_authorization_code reading the request line when the client disconnects abruptly, a port scanner or health probe opens and immediately closes the connection, or TLS is attempted against the plain-HTTP listener.

Common situations: Antivirus/security software probing the open port; browser preconnect connections torn down; an https:// redirect URI registered with the provider while the listener is plain http.

Related errors


AI-assisted analysis of Kuberwastaken/claurst@b0637c97ec (2026-09-10). Data as JSON: /api/errors/89fb6edcbafa3b9a. Report an issue: GitHub.

Appendix: source

Thrown at src-rust/crates/mcp/src/oauth.rs:255

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;
        }
    }

    let path = request_line.split_whitespace().nth(1).unwrap_or("");
    let parsed_url = url::Url::parse(&format!("http://{}{}", host, path))
        .map_err(|e| anyhow::anyhow!("Failed to parse OAuth callback URL '{}': {}", path, e))?;

    let response = "HTTP/1.1 200 OK\r\nContent-Type: text/plain; charset=utf-8\r\nConnection: close\r\n\r\nMCP OAuth authentication finished. You can close this tab.\r\n";
    writer
        .write_all(response.as_bytes())

View on GitHub (pinned to b0637c97ec)