{"record":{"id":"b40b5c7d109aab50","repo":"neondatabase/neon","slug":"gcs-get-response-contained-no-response-body","errorCode":null,"errorMessage":"GCS GET response contained no response body","messagePattern":"GCS GET response contained no response body","errorType":"exception","errorClass":"DownloadError","httpStatus":null,"severity":"error","filePath":"libs/remote_storage/src/gcs_bucket.rs","lineNumber":736,"sourceCode":"                    .map_err(|e: gcp_auth::Error| DownloadError::Other(e.into()))?\n                    .as_str(),\n            )\n            .send();\n\n        let obj_metadata = tokio::select! {\n            res = res => res,\n            _ = tokio::time::sleep(self.timeout) => return Err(DownloadError::Timeout),\n            _ = cancel.cancelled() => return Err(DownloadError::Cancelled),\n        };\n\n        let resp = match obj_metadata {\n            Ok(resp) => {\n                if !resp.status().is_success() {\n                    match resp.status() {\n                        StatusCode::NOT_FOUND => return Err(DownloadError::NotFound),\n                        _ => {\n                            return Err(DownloadError::Other(anyhow::anyhow!(\n                                \"GCS GET response contained no response body\"\n                            )));\n                        }\n                    }\n                } else {\n                    resp\n                }\n            }\n            _ => {\n                return Err(DownloadError::Other(anyhow::anyhow!(\"download gcs object\")));\n            }\n        };\n\n        let body = resp\n            .text()\n            .await\n            .map_err(|e: reqwest::Error| DownloadError::Other(e.into()))?;\n\n        let resp: GCSObject = serde_json::from_str(&body)","sourceCodeStart":718,"sourceCodeEnd":754,"githubUrl":"https://github.com/neondatabase/neon/blob/8f60b04da47ffefe0e52bda2440134b42874eb75/libs/remote_storage/src/gcs_bucket.rs#L718-L754","documentation":"During download, the first request (GET object metadata as JSON) returned a non-success status other than 404. Like the head path, the message is generic ('no response body') while the true cause is the discarded status code — auth failure, throttling, or a server error on the metadata GET.","triggerScenarios":"download() when the object metadata GET returns 401/403 (token expired between listing and download), 429/529, or 5xx — anything except 200 and 404.","commonSituations":"Tokens expiring mid-operation under load; GCS throttling when many downloads start simultaneously (permit-concurrency bursts); transient 5xx.","solutions":["Log the status code (patch the match arm or enable debug tracing) to classify auth vs throttle vs server error","Refresh credentials and retry the download","Backoff-retry on 429/5xx; fix permissions on 403","Check GCP status dashboard if 5xx persists cluster-wide"],"exampleFix":"// before: status discarded, error is opaque\n_ => return Err(DownloadError::Other(anyhow::anyhow!(\"GCS GET response contained no response body\"))),\n\n// after: include the status in the error\nstatus => return Err(DownloadError::Other(anyhow::anyhow!(\"GCS metadata GET failed: {}\", status))),","handlingStrategy":"retry","validationCode":null,"typeGuard":null,"tryCatchPattern":"// Download-time metadata failures: bounded retry, then propagate.\nasync fn download_with_retry(storage: &Arc<GenericRemoteStorage>, from: &RemotePath, cancel: &CancellationToken) -> Result<Download, DownloadError> {\n    let mut attempt = 0;\n    loop {\n        attempt += 1;\n        match storage.download(from, &cancel).await {\n            Ok(dl) => return Ok(dl),\n            Err(DownloadError::NotFound) => return Err(DownloadError::NotFound),\n            Err(DownloadError::Other(e)) if attempt < 3 => {\n                tracing::warn!(\"GCS metadata GET failed (attempt {attempt}): {e:#}\");\n                tokio::time::sleep(Duration::from_millis(300 * attempt as u64)).await;\n            }\n            Err(e) => return Err(e),\n        }\n    }\n}","preventionTips":["Stagger concurrent download starts (jitter) to avoid throttle bursts","Ensure the token provider refreshes ahead of expiry so mid-download 401s cannot happen","Route these failures into metrics — recurring 'no response body' errors are almost always throttling in disguise"],"tags":["gcs","google-cloud-storage","download","http-status","observability"],"backgroundTag":"http-error-response","analyzedSha":"8f60b04da47ffefe0e52bda2440134b42874eb75","analyzedAt":"2026-08-16T23:39:28.135Z","schemaVersion":2},"datasetVersion":"2026-08-17T04:17:16.089Z"}