{"record":{"id":"5a19808a478fc9e2","repo":"neondatabase/neon","slug":"received-listversions-response-for-key-key-with-5a1980","errorCode":null,"errorMessage":"Received ListVersions response for key={key} with version_id='null', indicating either disabled versioning, or legacy objects with null version id values","messagePattern":"Received ListVersions response for key=(.+?) with version_id='null', indicating either disabled versioning, or legacy objects with null version id values","errorType":"exception","errorClass":"TimeTravelError","httpStatus":null,"severity":"error","filePath":"libs/remote_storage/src/s3_bucket.rs","lineNumber":1021,"sourceCode":"            \"Built list for time travel with {} versions and deletions\",\n            versions_and_deletes.len()\n        );\n\n        // Work on the list of references instead of the objects directly,\n        // otherwise we get lifetime errors in the sort_by_key call below.\n        let mut versions_and_deletes = versions_and_deletes.iter().collect::<Vec<_>>();\n\n        versions_and_deletes.sort_by_key(|vd| (&vd.key, &vd.last_modified));\n\n        let mut vds_for_key = HashMap::<_, Vec<_>>::new();\n\n        for vd in &versions_and_deletes {\n            let Version { key, .. } = &vd;\n            let version_id = vd.version_id().map(|v| v.0.as_str());\n            if version_id == Some(\"null\") {\n                // TODO: check the behavior of using the SDK on a non-versioned container\n                return Err(TimeTravelError::Other(anyhow!(\n                    \"Received ListVersions response for key={key} with version_id='null', \\\n                    indicating either disabled versioning, or legacy objects with null version id values\"\n                )));\n            }\n            tracing::trace!(\"Parsing version key={key} kind={:?}\", vd.kind);\n\n            vds_for_key.entry(key).or_default().push(vd);\n        }\n\n        let warn_threshold = 3;\n        let max_retries = 10;\n        let is_permanent = |e: &_| matches!(e, TimeTravelError::Cancelled);\n\n        for (key, versions) in vds_for_key {\n            let last_vd = versions.last().unwrap();\n            let key = self.relative_path_to_s3_object(key);\n            if last_vd.last_modified > done_if_after {\n                tracing::trace!(\"Key {key} has version later than done_if_after, skipping\");\n                continue;","sourceCodeStart":1003,"sourceCodeEnd":1039,"githubUrl":"https://github.com/neondatabase/neon/blob/8f60b04da47ffefe0e52bda2440134b42874eb75/libs/remote_storage/src/s3_bucket.rs#L1003-L1039","documentation":"time_travel_recover lists every version under a prefix and refuses to continue if any version reports the literal version id \"null\". S3 uses \"null\" for objects written while bucket versioning was disabled or before it was enabled; such objects have no version history, so time travel is impossible and the operation aborts.","triggerScenarios":"Running time_travel_recover against a bucket where versioning was never enabled, a bucket holding legacy objects created before versioning was turned on, or an emulator that does not implement versioning and echoes 'null' as the version id.","commonSituations":"Enabling time-travel/retention features on a pre-existing non-versioned bucket; migrating from a plain S3 setup; running tests against non-versioning emulators.","solutions":["Enable versioning: aws s3api put-bucket-versioning --bucket <b> --versioning-configuration Status=Enabled","Objects with null version ids written before versioning cannot be time-traveled; rewrite/delete and re-upload them after enabling versioning","Create test buckets with versioning enabled from the start"],"exampleFix":"# before\naws s3api create-bucket --bucket mybucket\n# after\naws s3api create-bucket --bucket mybucket\naws s3api put-bucket-versioning --bucket mybucket --versioning-configuration Status=Enabled","handlingStrategy":"validation","validationCode":"use aws_sdk_s3::Client;\n\nasync fn versioning_enabled(client: &Client, bucket: &str) -> anyhow::Result<bool> {\n    let v = client.get_bucket_versioning().bucket(bucket).send().await?;\n    Ok(v.status().map(|s| s.as_str() == \"Enabled\").unwrap_or(false))\n}","typeGuard":"fn is_null_version_error(err: &remote_storage::TimeTravelError) -> bool {\n    matches!(err, remote_storage::TimeTravelError::Other(e)\n        if e.to_string().contains(\"version_id='null'\"))\n}","tryCatchPattern":"if let Err(TimeTravelError::Other(e)) = storage.time_travel_recover(&prefix, ts, done_if_after, &cancel, limit).await {\n    if e.to_string().contains(\"version_id='null'\") {\n        // bucket-level misconfiguration: enable versioning before retrying\n    }\n}","preventionTips":["Enable versioning at bucket creation in infrastructure-as-code","Add get_bucket_versioning assertions to deployment validation","Create test buckets with versioning enabled from the start"],"tags":["s3","rust","versioning","time-travel"],"backgroundTag":"s3-versioning-disabled","analyzedSha":"8f60b04da47ffefe0e52bda2440134b42874eb75","analyzedAt":"2026-08-16T23:39:28.135Z","schemaVersion":2},"datasetVersion":"2026-08-17T04:17:16.089Z"}