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

  1. Enable versioning: aws s3api put-bucket-versioning --bucket <b> --versioning-configuration Status=Enabled
  2. Objects with null version ids written before versioning cannot be time-traveled; rewrite/delete and re-upload them after enabling versioning
  3. 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

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


AI-assisted analysis of neondatabase/neon@8f60b04da4 (2026-08-16). Data as JSON: /api/errors/5a19808a478fc9e2. Report an issue: GitHub.