{"record":{"id":"82f0b3e3052b3edc","repo":"neondatabase/neon","slug":"head-object-doesn-t-contain-last-modified-or-conte","errorCode":null,"errorMessage":"head_object doesn't contain last_modified or content_length","messagePattern":"head_object doesn't contain last_modified or content_length","errorType":"exception","errorClass":"DownloadError","httpStatus":null,"severity":"error","filePath":"libs/remote_storage/src/s3_bucket.rs","lineNumber":823,"sourceCode":"                );\n                return Err(DownloadError::NotFound);\n            }\n            Err(e) => {\n                crate::metrics::BUCKET_METRICS.req_seconds.observe_elapsed(\n                    kind,\n                    AttemptOutcome::Err,\n                    started_at,\n                );\n\n                return Err(DownloadError::Other(\n                    anyhow::Error::new(e).context(\"s3 head object\"),\n                ));\n            }\n        };\n\n        let (Some(last_modified), Some(size)) = (data.last_modified, data.content_length) else {\n            return Err(DownloadError::Other(anyhow!(\n                \"head_object doesn't contain last_modified or content_length\"\n            )))?;\n        };\n        Ok(ListingObject {\n            key: key.to_owned(),\n            last_modified: SystemTime::try_from(last_modified).map_err(|e| {\n                DownloadError::Other(anyhow!(\"can't convert time '{last_modified}': {e}\"))\n            })?,\n            size: size as u64,\n        })\n    }\n\n    async fn upload(\n        &self,\n        from: impl Stream<Item = std::io::Result<Bytes>> + Send + Sync + 'static,\n        from_size_bytes: usize,\n        to: &RemotePath,\n        metadata: Option<StorageMetadata>,\n        cancel: &CancellationToken,","sourceCodeStart":805,"sourceCodeEnd":841,"githubUrl":"https://github.com/neondatabase/neon/blob/8f60b04da47ffefe0e52bda2440134b42874eb75/libs/remote_storage/src/s3_bucket.rs#L805-L841","documentation":"In the HEAD-based per-key listing helper, the HeadObject response must carry both last_modified and content_length to construct a ListingObject. If either is missing, the request technically succeeded but the endpoint returned an incomplete HeadObject response, which real S3 never does.","triggerScenarios":"Listing objects via the HeadObject path (list_files / listing flows that probe keys) against an S3-compatible emulator that omits Last-Modified or Content-Length in HeadObject responses.","commonSituations":"CI/dev with mocked S3 servers; gateways that synthesize HEAD responses from metadata-less storage without standard headers.","solutions":["Verify the endpoint returns both fields: aws s3api head-object --bucket <b> --key <k>","Switch dev/CI to real S3 or MinIO","Fix the emulator to set Last-Modified and Content-Length on HeadObject responses"],"exampleFix":"# before: incomplete HEAD from emulator\n$ aws s3api head-object --bucket b --key k --endpoint-url http://emulator:5000\n{}\n# after: compliant endpoint\n$ aws s3api head-object --bucket b --key k --endpoint-url http://minio:9000\n{\"LastModified\": \"...\", \"ContentLength\": 1024, ...}","handlingStrategy":"try-catch","validationCode":"use aws_sdk_s3::Client;\n\nasync fn head_is_complete(client: &Client, bucket: &str, key: &str) -> anyhow::Result<bool> {\n    let head = client.head_object().bucket(bucket).key(key).send().await?;\n    Ok(head.last_modified.is_some() && head.content_length.is_some())\n}","typeGuard":"fn is_incomplete_head_response(err: &remote_storage::DownloadError) -> bool {\n    matches!(err, remote_storage::DownloadError::Other(e)\n        if e.to_string().contains(\"head_object doesn't contain\"))\n}","tryCatchPattern":"match storage.list_files(&prefix, mode, &cancel).await {\n    Err(DownloadError::Other(e)) if e.to_string().contains(\"head_object doesn't contain\") => {\n        // emulator non-compliance: report endpoint, no retry\n    }\n    other => other?,\n}","preventionTips":["Probe emulators with head-object checks in CI setup","Treat missing standard HEAD metadata as an endpoint qualification failure"],"tags":["s3","rust","aws-sdk","remote-storage","http-header"],"backgroundTag":"missing-http-response-header","analyzedSha":"8f60b04da47ffefe0e52bda2440134b42874eb75","analyzedAt":"2026-08-16T23:39:28.135Z","schemaVersion":2},"datasetVersion":"2026-08-17T04:17:16.089Z"}