{"record":{"id":"906422f4ff113630","repo":"googleworkspace/cli","slug":"5","errorCode":"5","errorMessage":"Failed to list messages: {e}","messagePattern":"Failed to list messages: (.+?)","errorType":"exception","errorClass":"GwsError","httpStatus":null,"severity":"error","filePath":"crates/google-workspace-cli/src/helpers/gmail/triage.rs","lineNumber":59,"sourceCode":"    // gmail.metadata scope.  When a token carries both metadata and modify\n    // scopes the API may resolve to the metadata path and reject `q` with 403.\n    // gmail.readonly always supports `q`.\n    let token = auth::get_token(&[GMAIL_READONLY_SCOPE])\n        .await\n        .map_err(|e| GwsError::Auth(format!(\"Gmail auth failed: {e}\")))?;\n\n    let client = crate::client::build_client()?;\n\n    // 1. List message IDs\n    let list_url = \"https://gmail.googleapis.com/gmail/v1/users/me/messages\";\n\n    let list_resp = client\n        .get(list_url)\n        .query(&[(\"q\", query), (\"maxResults\", &max.to_string())])\n        .bearer_auth(&token)\n        .send()\n        .await\n        .map_err(|e| GwsError::Other(anyhow::anyhow!(\"Failed to list messages: {e}\")))?;\n\n    if !list_resp.status().is_success() {\n        let err = list_resp.text().await.unwrap_or_default();\n        return Err(GwsError::Api {\n            code: 0,\n            message: err,\n            reason: \"list_failed\".to_string(),\n            enable_url: None,\n        });\n    }\n\n    let list_json: Value = list_resp\n        .json()\n        .await\n        .map_err(|e| GwsError::Other(anyhow::anyhow!(\"Failed to parse list response: {e}\")))?;\n\n    let messages = match list_json.get(\"messages\").and_then(|m| m.as_array()) {\n        Some(m) => m,","sourceCodeStart":41,"sourceCodeEnd":77,"githubUrl":"https://github.com/googleworkspace/cli/blob/a3768d0e82ad83cca2da97724e46bea4ff0e6dbd/crates/google-workspace-cli/src/helpers/gmail/triage.rs#L41-L77","documentation":"In `+triage` (triage.rs), the initial `GET /gmail/v1/users/me/messages?q=...&maxResults=...` failed at the transport level (`client.get(...).send()` — note: no `send_with_retry` wrapper, unlike other gmail helpers, so the FIRST transient error aborts). This is a reqwest send error: DNS, connect, TLS, or mid-request disconnect. HTTP failures take the `GwsError::Api { reason: \"list_failed\" }` branch instead.","triggerScenarios":"Running `gws gmail +triage` with a momentary network blip — because there is no retry, even a single dropped connection fails the whole triage session; offline runs; proxy blocking gmail.googleapis.com.","commonSituations":"Interactive triage over wifi where each keypress fetches messages; any environment where the other helpers survive thanks to send_with_retry but +triage dies on the first hiccup.","solutions":["Simply re-run the triage command — a single transient failure is the top cause and retrying usually works.","Verify connectivity to gmail.googleapis.com.","If triage repeatedly fails while `gws gmail users.messages list` works, this confirms the no-retry gap; update the CLI or patch triage.rs to use `crate::client::send_with_retry` like mod.rs does (see example fix).","Check proxy env vars in the shell running gws."],"exampleFix":"// before: single-shot send, no retry\nlet list_resp = client\n    .get(list_url)\n    .query(&[(\"q\", query), (\"maxResults\", &max.to_string())])\n    .bearer_auth(&token)\n    .send()\n    .await\n    .map_err(|e| GwsError::Other(anyhow::anyhow!(\"Failed to list messages: {e}\")))?;\n\n// after: use the shared retry helper like the other gmail helpers\nlet list_resp = crate::client::send_with_retry(|| {\n    client\n        .get(list_url)\n        .query(&[(\"q\", query), (\"maxResults\", &max.to_string())])\n        .bearer_auth(&token)\n})\n.await\n.map_err(|e| GwsError::Other(anyhow::anyhow!(\"Failed to list messages: {e}\")))?;","handlingStrategy":"retry","validationCode":"// Unlike sibling helpers, +triage's list call is single-shot — wrap your invocations with retry\nfor attempt in 0..3 {\n    match run_triage(&args).await {\n        Err(e) if is_transport_error(&e) && attempt < 2 => sleep_backoff(attempt).await,\n        other => return other,\n    }\n}","typeGuard":"fn is_transport_error(e: &GwsError) -> bool {\n    matches!(e, GwsError::Other(_)) && e.to_string().starts_with(\"Failed to list messages\")\n}","tryCatchPattern":"// Inside a patched triage.rs, replace the single-shot send with the shared retry helper:\nlet list_resp = crate::client::send_with_retry(|| {\n    client.get(list_url).query(&[(\"q\", query), (\"maxResults\", &max.to_string())]).bearer_auth(&token)\n}).await.map_err(|e| GwsError::Other(anyhow::anyhow!(\"Failed to list messages: {e}\")))?;","preventionTips":["Expect zero-retry behavior from +triage's initial list call — a single blip aborts; just re-run.","In scripts driving +triage, add your own retry-with-backoff around the command.","Contributors: route every googleapis call through crate::client::send_with_retry for uniform retry semantics."],"tags":["gmail","triage","network","no-retry","messages-list"],"backgroundTag":"http-request-failed","analyzedSha":"a3768d0e82ad83cca2da97724e46bea4ff0e6dbd","analyzedAt":"2026-08-16T19:51:46.516Z","schemaVersion":2},"datasetVersion":"2026-08-16T23:17:17.608Z"}