{"record":{"id":"980279e0d51cf279","repo":"sigoden/aichat","slug":"err-msg","errorCode":null,"errorMessage":"{err_msg}","messagePattern":"\\{err_msg\\}","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"critical","filePath":"src/client/vertexai.rs","lineNumber":485,"sourceCode":"async fn fetch_access_token(\n    client: &reqwest::Client,\n    file: &Option<String>,\n) -> Result<(String, i64)> {\n    let credentials = load_adc(file).await?;\n    let value: Value = client\n        .post(\"https://oauth2.googleapis.com/token\")\n        .json(&credentials)\n        .send()\n        .await?\n        .json()\n        .await?;\n\n    if let (Some(access_token), Some(expires_in)) =\n        (value[\"access_token\"].as_str(), value[\"expires_in\"].as_i64())\n    {\n        Ok((access_token.to_string(), expires_in))\n    } else if let Some(err_msg) = value[\"error_description\"].as_str() {\n        bail!(\"{err_msg}\")\n    } else {\n        bail!(\"Invalid response data: {value}\")\n    }\n}\n\nasync fn load_adc(file: &Option<String>) -> Result<Value> {\n    let adc_file = file\n        .as_ref()\n        .map(PathBuf::from)\n        .or_else(default_adc_file)\n        .ok_or_else(|| anyhow!(\"No application_default_credentials.json\"))?;\n    let data = tokio::fs::read_to_string(adc_file).await?;\n    let data: Value = serde_json::from_str(&data)?;\n    if let (Some(client_id), Some(client_secret), Some(refresh_token)) = (\n        data[\"client_id\"].as_str(),\n        data[\"client_secret\"].as_str(),\n        data[\"refresh_token\"].as_str(),\n    ) {","sourceCodeStart":467,"sourceCodeEnd":503,"githubUrl":"https://github.com/sigoden/aichat/blob/82976d349ad97ac9aae0655ad631dace5e2a6385/src/client/vertexai.rs#L467-L503","documentation":"Thrown by fetch_access_token in src/client/vertexai.rs:485 when the OAuth2 token endpoint responds with a JSON body containing an `error_description` field instead of a valid access_token/expires_in pair. The library propagates Google's own error description verbatim as the error message. This is an authentication failure — Google rejected the token exchange for the service account credentials being used.","triggerScenarios":"Calling prepare_gcloud_access_token -> fetch_access_token where the token endpoint returns e.g. {\"error\":\"invalid_grant\",\"error_description\":\"Invalid JWT Signature.\"} — caused by expired/rotated service account keys, wrong private_key in the credentials JSON, badly skewed system clock, or the service account being disabled/deleted.","commonSituations":"Rotated or deleted service account keys while old GOOGLE_APPLICATION_CLOUD credentials JSON still on disk; misconfigured GOOGLE_APPLICATION_CREDENTIALS pointing to the wrong file; clock drift on the machine invalidating signed JWTs; disabled service accounts or missing IAM roles on the project.","solutions":["Read the error_description in the message (e.g. \"invalid_grant\") and fix the underlying Google auth problem it names.","Regenerate the service account key and update the credentials JSON / GOOGLE_APPLICATION_CREDENTIALS path.","Check the system clock — significant skew invalidates the signed JWT; run NTP sync.","Verify the service account exists and is enabled in the Google Cloud console with correct IAM roles.","Re-run gcloud auth application-default login if relying on Application Default Credentials."],"exampleFix":"// before (stale key in credentials.json)\n// error: Invalid JWT Signature.\n// after\n// regenerate key:\n//   gcloud iam service-accounts keys create new-key.json \\\n//     --iam-account=my-sa@project.iam.gserviceaccount.com\n// then set GOOGLE_APPLICATION_CREDENTIALS=/path/to/new-key.json","handlingStrategy":"retry","validationCode":"// Validate the ADC credentials file before attempting the token exchange\nfn validate_adc_file(path: &str) -> anyhow::Result<()> {\n    let v: serde_json::Value = serde_json::from_str(&std::fs::read_to_string(path)?)?;\n    let key_ok = v[\"private_key\"].as_str().map_or(false, |k| k.contains(\"BEGIN PRIVATE KEY\"));\n    let acct_ok = v[\"client_email\"].as_str().map_or(false, |e| e.ends_with(\".iam.gserviceaccount.com\"));\n    anyhow::ensure!(key_ok && acct_ok, \"credentials file missing valid private_key/client_email\");\n    Ok(())\n}","typeGuard":null,"tryCatchPattern":"let token = loop {\n    match prepare_gcloud_access_token().await {\n        Ok(t) => break t,\n        Err(e) if is_invalid_grant(&e.to_string()) => {\n            rotate_service_account_key()?; // or alert ops, then retry once\n            attempts += 1;\n            if attempts > 1 { return Err(e.into()); }\n        }\n        Err(e) => return Err(e.into()),\n    }\n};","preventionTips":["Rotate service account keys on a schedule and update credentials files before old keys expire.","Keep the host clock accurate via NTP — clock skew invalidates signed JWTs (invalid_grant).","Verify GOOGLE_APPLICATION_CREDENTIALS points to a current, enabled service account key file.","Check the Google Cloud console that the service account exists, is enabled, and has needed IAM roles.","Surface the error_description from the message to ops tooling for fast diagnosis of auth failures."],"tags":["oauth","authentication","google-cloud","vertex-ai","token"],"backgroundTag":"oauth-token-exchange-failed","analyzedSha":"82976d349ad97ac9aae0655ad631dace5e2a6385","analyzedAt":"2026-09-09T18:33:06.139Z","contentChangedAt":"2026-09-09T18:33:06.139Z","schemaVersion":2},"datasetVersion":"2026-09-16T09:17:16.951Z"}