neondatabase/neon · error · TimeTravelError
Received ListVersions response for key={key} with version_id
Error message
Received ListVersions response for key={key} with version_id='null', indicating either disabled versioning, or legacy objects with null version id values What it means
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.
Source
Thrown at libs/remote_storage/src/s3_bucket.rs:1021
"Built list for time travel with {} versions and deletions",
versions_and_deletes.len()
);
// Work on the list of references instead of the objects directly,
// otherwise we get lifetime errors in the sort_by_key call below.
let mut versions_and_deletes = versions_and_deletes.iter().collect::<Vec<_>>();
versions_and_deletes.sort_by_key(|vd| (&vd.key, &vd.last_modified));
let mut vds_for_key = HashMap::<_, Vec<_>>::new();
for vd in &versions_and_deletes {
let Version { key, .. } = &vd;
let version_id = vd.version_id().map(|v| v.0.as_str());
if version_id == Some("null") {
// TODO: check the behavior of using the SDK on a non-versioned container
return Err(TimeTravelError::Other(anyhow!(
"Received ListVersions response for key={key} with version_id='null', \
indicating either disabled versioning, or legacy objects with null version id values"
)));
}
tracing::trace!("Parsing version key={key} kind={:?}", vd.kind);
vds_for_key.entry(key).or_default().push(vd);
}
let warn_threshold = 3;
let max_retries = 10;
let is_permanent = |e: &_| matches!(e, TimeTravelError::Cancelled);
for (key, versions) in vds_for_key {
let last_vd = versions.last().unwrap();
let key = self.relative_path_to_s3_object(key);
if last_vd.last_modified > done_if_after {
tracing::trace!("Key {key} has version later than done_if_after, skipping");
continue;View on GitHub (pinned to 8f60b04da4)
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
Example fix
# before aws s3api create-bucket --bucket mybucket # after aws s3api create-bucket --bucket mybucket aws s3api put-bucket-versioning --bucket mybucket --versioning-configuration Status=Enabled
Defensive patterns
Strategy: validation
Validate before calling
use aws_sdk_s3::Client;
async fn versioning_enabled(client: &Client, bucket: &str) -> anyhow::Result<bool> {
let v = client.get_bucket_versioning().bucket(bucket).send().await?;
Ok(v.status().map(|s| s.as_str() == "Enabled").unwrap_or(false))
} Type guard
fn is_null_version_error(err: &remote_storage::TimeTravelError) -> bool {
matches!(err, remote_storage::TimeTravelError::Other(e)
if e.to_string().contains("version_id='null'"))
} Try / catch
if let Err(TimeTravelError::Other(e)) = storage.time_travel_recover(&prefix, ts, done_if_after, &cancel, limit).await {
if e.to_string().contains("version_id='null'") {
// bucket-level misconfiguration: enable versioning before retrying
}
} Prevention
- 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
When it happens
Trigger: 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.
Common situations: Enabling time-travel/retention features on a pre-existing non-versioned bucket; migrating from a plain S3 setup; running tests against non-versioning emulators.
Related errors
- too many versions
- Read back file doesn't match original
- Received ListVersions response for key={key} with version_id
- Received ListVersions response for key={key} with version_id
- Missing ETag header
AI-assisted analysis of neondatabase/neon@8f60b04da4 (2026-08-16).
Data as JSON: /api/errors/5a19808a478fc9e2.
Report an issue: GitHub.