{"record":{"id":"cd8ff69063f49b08","repo":"googleworkspace/cli","slug":"failed-to-fetch-attachment-e","errorCode":null,"errorMessage":"Failed to fetch attachment: {e}","messagePattern":"Failed to fetch attachment: (.+?)","errorType":"exception","errorClass":"GwsError","httpStatus":null,"severity":"error","filePath":"crates/google-workspace-cli/src/helpers/gmail/mod.rs","lineNumber":702,"sourceCode":"/// Fetch binary data for a single attachment from the Gmail API.\n///\n/// Calls `GET /users/me/messages/{messageId}/attachments/{attachmentId}`,\n/// decodes the base64url `data` field, and returns raw bytes.\nasync fn fetch_attachment_data(\n    client: &reqwest::Client,\n    token: &str,\n    message_id: &str,\n    attachment_id: &str,\n) -> Result<Vec<u8>, GwsError> {\n    let url = format!(\n        \"https://gmail.googleapis.com/gmail/v1/users/me/messages/{}/attachments/{}\",\n        crate::validate::encode_path_segment(message_id),\n        crate::validate::encode_path_segment(attachment_id),\n    );\n\n    let resp = crate::client::send_with_retry(|| client.get(&url).bearer_auth(token))\n        .await\n        .map_err(|e| GwsError::Other(anyhow::anyhow!(\"Failed to fetch attachment: {e}\")))?;\n\n    if !resp.status().is_success() {\n        let status = resp.status().as_u16();\n        let err = resp\n            .text()\n            .await\n            .unwrap_or_else(|_| \"(error body unreadable)\".to_string());\n        return Err(build_api_error(\n            status,\n            &err,\n            &format!(\"Failed to fetch attachment {attachment_id} from message {message_id}\"),\n        ));\n    }\n\n    let body: Value = resp\n        .json()\n        .await\n        .map_err(|e| GwsError::Other(anyhow::anyhow!(\"Failed to parse attachment JSON: {e}\")))?;","sourceCodeStart":684,"sourceCodeEnd":720,"githubUrl":"https://github.com/googleworkspace/cli/blob/a3768d0e82ad83cca2da97724e46bea4ff0e6dbd/crates/google-workspace-cli/src/helpers/gmail/mod.rs#L684-L720","documentation":"Transport-level failure while downloading an attachment: `send_with_retry` on `GET /gmail/v1/users/me/messages/{messageId}/attachments/{attachmentId}` never produced an HTTP response after retries. Both path segments are percent-encoded via `encode_path_segment`, so URL shape is safe; the failure is DNS/connect/TLS/transport. HTTP error statuses become `build_api_error` instead.","triggerScenarios":"Downloading a large attachment over a dropping connection until retries exhaust; offline `+forward --attach-original`; proxy blocking the attachments path pattern.","commonSituations":"Forwarding messages with big attachments on hotel wifi; CI jobs pulling attachments where egress is rate-limited; mobile tethering.","solutions":["Re-run the forward/send — attachment fetch is per-part and restartable.","Stabilize the connection or switch networks before retrying large downloads.","Use `--attach` size limits / avoid `--attach-original` for huge parts if the network is unreliable.","Check proxy rules for the `/attachments/` URL path."],"exampleFix":null,"handlingStrategy":"retry","validationCode":"// Before --attach-original flows, confirm attachment endpoints are reachable and the part still exists\nlet part = find_part_by_attachment_id(&msg, attachment_id);\nif part.is_none() { return Err(anyhow::anyhow!(\"attachment {attachment_id} no longer listed\")); }","typeGuard":null,"tryCatchPattern":"// Attachment downloads are per-part: retry the failing part, not the whole message fetch\nfor attempt in 0..3 {\n    match fetch_attachment(client, token, message_id, attachment_id).await {\n        Ok(bytes) => return Ok(bytes),\n        Err(GwsError::Other(_)) if attempt < 2 => tokio::time::sleep(Duration::from_secs(2u64.pow(attempt))).await,\n        Err(e) => return Err(e),\n    }\n}","preventionTips":["Re-fetch message metadata before downloading attachments to confirm attachmentIds are still valid.","Retry per-part with exponential backoff; parts are independent.","Avoid --attach-original for very large parts on unreliable links."],"tags":["gmail","attachment","network","reqwest","download"],"backgroundTag":"http-request-failed","analyzedSha":"a3768d0e82ad83cca2da97724e46bea4ff0e6dbd","analyzedAt":"2026-08-16T19:51:46.516Z","schemaVersion":2},"datasetVersion":"2026-08-16T23:17:17.608Z"}