Hmbown/CodeWhale · error

returned HTTP with content type ; expected JSON

Error message

{operation} returned HTTP {status} with content type {content_type}; expected JSON{limit}

What it means

`parse_oauth_json` validates the raw HTTP response from an OAuth discovery/device-grant/polling request. When the body cannot be parsed as JSON, it fails with the HTTP status, the response content type, and a note on whether the body exceeded the 64 KiB diagnostic limit — so the developer can see whether the server returned an HTML error page, an oversized body, or some other non-JSON payload.

Solutions

  1. Check the reported status/content-type: an HTML 4xx/5xx means the endpoint or network path is wrong — fix the issuer/endpoint URL or proxy
  2. Open the OAuth endpoint in a browser/curl and confirm it returns application/json
  3. If the body exceeded 64 KiB, fetch the endpoint directly and inspect what oversized payload is being served

Example fix

// before: issuer points at an HTML landing page
"issuer": "https://example.com"
// after: use the real authorization server root
"issuer": "https://auth.example.com"
Defensive patterns

Strategy: try-catch

Validate before calling

let resp = reqwest::get(oauth_endpoint).await?;
let ct = resp.headers().get(CONTENT_TYPE).and_then(|v| v.to_str().ok()).unwrap_or("");
let body = resp.text().await?;
if !ct.starts_with("application/json") {
    anyhow::bail!("endpoint did not return JSON (content-type: {ct}) — check issuer/proxy");
}
if body.len() > 64 * 1024 { anyhow::bail!("response exceeds 64 KiB diagnostic limit"); }
serde_json::from_str::<serde_json::Value>(&body)?;

Try / catch

match discover_oauth_endpoints(issuer) {
    Ok(eps) => eps,
    Err(e) if e.to_string().contains("expected JSON") => {
        // message embeds status + content type; check for HTML/proxy pages
        inspect_endpoint_rawly(issuer)?;
        Err(e)
    }
    Err(e) => Err(e),
}

Prevention

When it happens

Trigger: `discover_oauth_endpoints`, `request_device_grant`, or `poll_device_grant` receives an HTTP response whose body fails `serde_json::from_slice` — e.g. an HTML error page from a proxy, a 502 gateway page, a text/plain message, or a body over 64 KiB (flagged as truncated).

Common situations: Corporate proxy/captive portal intercepting the OAuth request and returning HTML; wrong issuer URL pointing at a non-OAuth endpoint; server down with an HTML maintenance page; a misconfigured content type on a JSON endpoint that actually returns form-encoded data.

Understand the failure class

Background: "Invalid JSON response" and "Failed to parse response" errors: when an API answers 200 but the body isn't the JSON your library expected — this error's family across 28 libraries.

Related errors


AI-assisted analysis of Hmbown/CodeWhale@73e0f67d83 (2026-09-22). Data as JSON: /api/errors/4b365eb3e76abf1c. Report an issue: GitHub.

Appendix: source

Thrown at crates/tui/src/oauth.rs:533

            joined
        }
    };
    let mut reader = response.take(OAUTH_RESPONSE_BODY_LIMIT + 1);
    let mut body = Vec::new();
    reader
        .read_to_end(&mut body)
        .with_context(|| format!("reading {operation} response"))?;
    let truncated = body.len() as u64 > OAUTH_RESPONSE_BODY_LIMIT;
    if truncated {
        body.truncate(OAUTH_RESPONSE_BODY_LIMIT as usize);
    }
    let parsed = serde_json::from_slice(&body).map_err(|_| {
        let limit = if truncated {
            " (body exceeded the 64 KiB diagnostic limit)"
        } else {
            ""
        };
        anyhow::anyhow!(
            "{operation} returned HTTP {status} with content type {content_type}; expected JSON{limit}"
        )
    })?;
    Ok((status, parsed))
}

fn bounded_oauth_error_text(raw: &str) -> String {
    let mut output = String::with_capacity(raw.len().min(OAUTH_ERROR_DETAIL_LIMIT));
    let mut previous_was_space = false;
    let mut written = 0;
    for character in raw.chars() {
        let character = if character.is_whitespace() {
            ' '
        } else if character.is_control() {
            continue;
        } else {
            character
        };

View on GitHub (pinned to 73e0f67d83)