Kuberwastaken/claurst · error · anyhow::Error

Invalid HTTP request

Error message

Invalid HTTP request

What it means

The loopback HTTP server accepted the browser's OAuth callback connection but the first request line could not be parsed as `GET /auth/callback?...`. Means the client sent a malformed or non-HTTP request (e.g. a health probe, prefetch, or telnet-style connection) to the local callback port.

Solutions

  1. Retry the login flow; the browser usually sends a well-formed request on the next attempt
  2. Ensure nothing else (proxy, antivirus, another app) is hitting 127.0.0.1 on the callback port
  3. If it persists, capture the raw request line to identify what client is connecting
Defensive patterns

Strategy: validation

When it happens

Trigger: Thrown at src-rust/crates/cli/src/codex_oauth_flow.rs:130 when the library encounters an invalid state.

Common situations: See trigger scenarios.


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

Appendix: source

Thrown at src-rust/crates/cli/src/codex_oauth_flow.rs:130

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();
    let mut error = String::new();

    for param in query.split('&') {
        let kv: Vec<&str> = param.splitn(2, '=').collect();
        if kv.len() == 2 {
            match kv[0] {
                "code" => code = urlencoding::decode(kv[1])?.to_string(),
                "state" => state = urlencoding::decode(kv[1])?.to_string(),
                "error" => error = urlencoding::decode(kv[1])?.to_string(),
                "error_description" => error = urlencoding::decode(kv[1])?.to_string(),

View on GitHub (pinned to b0637c97ec)