sigoden/aichat · critical

Invalid response data

Error message

Invalid response data: {value}

What it means

Thrown by fetch_access_token in src/client/vertexai.rs:487 when the OAuth2 token response JSON contains neither a valid access_token/expires_in pair nor an error_description field. The library embeds the entire raw response value in the message because it cannot interpret what the token endpoint returned. It indicates an unexpected response shape from Google's OAuth token endpoint — likely a proxy, wrong URL, or API change.

Solutions

  1. Inspect the raw `value` JSON in the error message to see what the token endpoint actually returned.
  2. Check network path — disable/inspect corporate proxies or VPNs intercepting https traffic to oauth2.googleapis.com.
  3. Verify the token_uri field in the service account credentials JSON points to https://oauth2.googleapis.com/token.
  4. Retry — transient Google 5xx responses can produce bodies without access_token.
  5. Re-download the service account credentials JSON to ensure it has a valid token_uri and key fields.

Example fix

// before
// credentials.json with token_uri: "https://wrong-endpoint.example.com/token"
// after
// credentials.json with token_uri: "https://oauth2.googleapis.com/token"
Defensive patterns

Strategy: try-catch

Validate before calling

// Pre-flight: ensure the credentials JSON has a sane token_uri before requesting a token
fn validate_token_uri(creds: &serde_json::Value) -> anyhow::Result<()> {
    let uri = creds["token_uri"].as_str().unwrap_or_default();
    anyhow::ensure!(uri == "https://oauth2.googleapis.com/token", "unexpected token_uri: {uri}");
    Ok(())
}

Try / catch

let token = match prepare_gcloud_access_token().await {
    Ok(t) => t,
    Err(e) if e.to_string().contains("Invalid response data") => {
        // likely proxy/HTML interception or transient Google 5xx
        tokio::time::sleep(Duration::from_secs(2)).await;
        prepare_gcloud_access_token().await.map_err(|e2| anyhow::anyhow!("token exchange returned unexpected body: {e2}"))?
    }
    Err(e) => return Err(e.into()),
};

Prevention

When it happens

Trigger: Calling prepare_gcloud_access_token -> fetch_access_token where the parsed response JSON lacks access_token and expires_in and also lacks error_description — e.g. a corporate proxy returning an HTML login page, a typo'd token endpoint URL, a 500 JSON body with only {"error":"internal_error"}, or a captive portal response.

Common situations: Corporate proxies/firewalls intercepting the token request; misconfigured token_uri in the service account credentials JSON; Google-side transient 5xx with unexpected bodies; VPN/captive-portal environments; credentials JSON from a non-Google or wrong-format file.

Understand the failure class

Background: "invalid response format", "malformed payload", "missing data field": when an API returns 200 but the response shape is wrong — this error's family across 23 libraries.

Related errors


AI-assisted analysis of sigoden/aichat@82976d349a (2026-09-09). Data as JSON: /api/errors/f16b297a1d6b3ab0. Report an issue: GitHub.

Appendix: source

Thrown at src/client/vertexai.rs:487

    file: &Option<String>,
) -> Result<(String, i64)> {
    let credentials = load_adc(file).await?;
    let value: Value = client
        .post("https://oauth2.googleapis.com/token")
        .json(&credentials)
        .send()
        .await?
        .json()
        .await?;

    if let (Some(access_token), Some(expires_in)) =
        (value["access_token"].as_str(), value["expires_in"].as_i64())
    {
        Ok((access_token.to_string(), expires_in))
    } else if let Some(err_msg) = value["error_description"].as_str() {
        bail!("{err_msg}")
    } else {
        bail!("Invalid response data: {value}")
    }
}

async fn load_adc(file: &Option<String>) -> Result<Value> {
    let adc_file = file
        .as_ref()
        .map(PathBuf::from)
        .or_else(default_adc_file)
        .ok_or_else(|| anyhow!("No application_default_credentials.json"))?;
    let data = tokio::fs::read_to_string(adc_file).await?;
    let data: Value = serde_json::from_str(&data)?;
    if let (Some(client_id), Some(client_secret), Some(refresh_token)) = (
        data["client_id"].as_str(),
        data["client_secret"].as_str(),
        data["refresh_token"].as_str(),
    ) {
        Ok(json!({
            "client_id": client_id,

View on GitHub (pinned to 82976d349a)