sigoden/aichat · error · anyhow::Error

Failed to parse expires_in of access_token

Error message

Failed to parse expires_in of access_token

What it means

In src/client/vertexai.rs `prepare_gcloud_access_token`, the `expires_in` value returned from fetching a Google access token could not be converted into a valid `Duration` (e.g. it was zero, negative, or overflowed). The library throws this instead of caching an invalid token expiry.

Solutions

  1. Re-run `gcloud auth application-default login` to regenerate a valid application_default_credentials.json.
  2. Check the ADC file's refresh_token/client_id/client_secret are real values, not placeholders.
  3. Capture the raw token endpoint response to see what expires_in is actually being returned.
Defensive patterns

Strategy: validation

Validate before calling

// Check ADC validity before calling the provider
let adc = std::path::Path::new(&format!("{}/.config/gcloud/application_default_credentials.json", home));
assert!(adc.exists(), "run: gcloud auth application-default login");

Try / catch

// Rust
do_vertexai_call().await.map_err(|e| {
    if e.to_string().contains("expires_in") { /* re-auth: gcloud auth application-default login */ }
    e
})?;

Prevention

When it happens

Trigger: `fetch_access_token` returns an `expires_in` that `Duration::try_seconds` rejects (None): typically 0, negative, or an implausible value parsed from the token endpoint / ADC refresh response.

Common situations: Misconfigured ADC refresh token causing a degenerate token response; a fake/mock ADC file; clock or response-shape anomalies from the OAuth endpoint.

Understand the failure class

Related errors


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

Appendix: source

Thrown at src/client/vertexai.rs:461

            Ok(ModelCategory::Mistral)
        } else {
            unsupported_model!(s)
        }
    }
}

pub async fn prepare_gcloud_access_token(
    client: &reqwest::Client,
    client_name: &str,
    adc_file: &Option<String>,
) -> Result<()> {
    if !is_valid_access_token(client_name) {
        let (token, expires_in) = fetch_access_token(client, adc_file)
            .await
            .with_context(|| "Failed to fetch access token")?;
        let expires_at = Utc::now()
            + Duration::try_seconds(expires_in)
                .ok_or_else(|| anyhow!("Failed to parse expires_in of access_token"))?;
        set_access_token(client_name, token, expires_at.timestamp())
    }
    Ok(())
}

async fn fetch_access_token(
    client: &reqwest::Client,
    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?;

View on GitHub (pinned to 82976d349a)