{"record":{"id":"2da1a55f0cd9f053","repo":"neondatabase/neon","slug":"received-listversions-response-for-key-key-with","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/azure_blob.rs","lineNumber":988,"sourceCode":"        tracing::info!(\n            \"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                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_name(key);\n            if last_vd.last_modified > done_if_after {\n                tracing::debug!(\"Key {key} has version later than done_if_after, skipping\");\n                continue;","sourceCodeStart":970,"sourceCodeEnd":1006,"githubUrl":"https://github.com/neondatabase/neon/blob/8f60b04da47ffefe0e52bda2440134b42874eb75/libs/remote_storage/src/azure_blob.rs#L970-L1006","documentation":"Neon time travel lists blob versions on Azure and refuses the response when any version reports the literal version id \\\"null\\\". On Azure, a null version id means blob versioning is disabled on the storage account (only an unversioned base blob exists) or the blob predates versioning being enabled. Time travel needs a real version id for every version, so it aborts the whole listing.","triggerScenarios":"Calling list_versions / time-travel APIs against an Azure storage account or container where versioning was never enabled, or which still contains blobs written before versioning was turned on.","commonSituations":"Pointing a time-travel-capable component at a plain storage account created without versioning; test/dev accounts; accounts where versioning was enabled after data was already written; manual uploads done outside the versioned pipeline.","solutions":["Enable blob versioning on the storage account (Azure Portal: Data protection > Versioning, or az storage account blob-service-properties update --enable-versioning true)","Use an account/container that has had versioning enabled since before any data was written","Rewrite/migrate affected objects so fresh, versioned copies exist","If time travel is not required for these paths, route them to a non-time-travel listing path"],"exampleFix":null,"handlingStrategy":"validation","validationCode":"// Before using time travel, confirm blob versioning is enabled on the storage account.\n// CLI equivalent: az storage account blob-service-properties show --account-name <acct>\n// (look for \"isVersioningEnabled\": true)\nasync fn assert_azure_versioning(account: &str) -> anyhow::Result<()> {\n    let props = az_cli_json([\"storage\", \"account\", \"blob-service-properties\", \"show\", \"--account-name\", account]).await?;\n    let enabled = props.pointer(\"/isVersioningEnabled\").and_then(|v| v.as_bool()).unwrap_or(false);\n    anyhow::ensure!(enabled, \"blob versioning disabled on account {account}; time travel requires it\");\n    Ok(())\n}","typeGuard":null,"tryCatchPattern":"// Detect the condition and fail fast with an actionable message.\nlet versions = match storage.list_versions(key, None, &cancel).await {\n    Ok(v) => v,\n    Err(TimeTravelError::Other(e)) if format!(\"{e:#}\").contains(\"version_id='null'\") => {\n        return Err(anyhow::anyhow!(\"bucket is not version-enabled (or holds pre-versioning blobs); enable versioning on the storage account\"));\n    }\n    Err(e) => return Err(e.into()),\n};","preventionTips":["Enable blob versioning at account creation time, not after data lands","Only write time-travel-relevant data to version-enabled containers","Add a startup config check that verifies versioning before serving time-travel requests"],"tags":["azure","blob-storage","versioning","time-travel","configuration"],"backgroundTag":"versioning-disabled","analyzedSha":"8f60b04da47ffefe0e52bda2440134b42874eb75","analyzedAt":"2026-08-16T23:39:28.135Z","schemaVersion":2},"datasetVersion":"2026-08-17T04:17:16.089Z"}