neondatabase/neon · warning

list_files: failed to find valid ancestor dir for {full_path

Error message

list_files: failed to find valid ancestor dir for {full_path}

What it means

local_fs's list_recursive must start scanning from an existing directory: S3-style prefixes are arbitrary strings, but a filesystem read_dir needs a real directory. It walks upward from the requested prefix toward the root until it finds one; this error means it reached the filesystem root without any ancestor existing — practically, not even the first path component of the prefix exists under storage_root.

Source

Thrown at libs/remote_storage/src/local_fs.rs:168

        };

        // If we were given a directory, we may use it as our starting point.
        // Otherwise, we must go up to the first ancestor dir that exists.  This is because
        // S3 object list prefixes can be arbitrary strings, but when reading
        // the local filesystem we need a directory to start calling read_dir on.
        let mut initial_dir = full_path.clone();

        // If there's no trailing slash, we have to start looking from one above: even if
        // `initial_dir` is a directory, we should still list any prefixes in the parent
        // that start with the same string.
        if !full_path.to_string().ends_with('/') {
            initial_dir.pop();
        }

        loop {
            // Did we make it to the root?
            if initial_dir.parent().is_none() {
                anyhow::bail!("list_files: failed to find valid ancestor dir for {full_path}");
            }

            match fs::metadata(initial_dir.clone()).await {
                Ok(meta) if meta.is_dir() => {
                    // We found a directory, break
                    break;
                }
                Ok(_meta) => {
                    // It's not a directory: strip back to the parent
                    initial_dir.pop();
                }
                Err(e) if e.kind() == ErrorKind::NotFound => {
                    // It's not a file that exists: strip the prefix back to the parent directory
                    initial_dir.pop();
                }
                Err(e) => {
                    // Unexpected I/O error
                    anyhow::bail!(e)

View on GitHub (pinned to 8f60b04da4)

Solutions

  1. Treat this error as an empty listing at the call site — if no ancestor directory exists, no files can exist under the prefix
  2. Ensure the directory tree is created before listing (the upload path creates dirs on demand)
  3. Verify storage_root points at the intended, populated directory
  4. If arbitrary-prefix listing must not fail, pre-create the top-level directory layout

Example fix

// before: empty tree turns into a hard error
let files = local_fs.list_files(Some(&prefix), None, &cancel).await?;

// after: map 'no ancestor dir' to an empty result
let files = match local_fs.list_files(Some(&prefix), None, &cancel).await {
    Ok(files) => files,
    Err(e) if e.to_string().contains("failed to find valid ancestor dir") => Vec::new(),
    Err(e) => return Err(e),
};
Defensive patterns

Strategy: validation

Validate before calling

// If no ancestor of the prefix exists, the listing is definitionally empty — check first.
async fn first_existing_ancestor(storage_root: &Utf8Path, prefix: &RemotePath) -> Option<Utf8PathBuf> {
    let mut dir = prefix.with_base(storage_root);
    loop {
        if tokio::fs::try_exists(&dir).await.ok()? {
            return Some(dir);
        }
        if !dir.pop() { return None; }
    }
}
// caller: if first_existing_ancestor(...).is_none() { return Ok(Vec::new()); }

Try / catch

// Recognize the 'nothing exists yet' bail and map it to an empty listing.
let files = match local_fs.list_files(Some(&prefix), None, &cancel).await {
    Ok(files) => files,
    Err(e) if format!("{e:#}").contains("failed to find valid ancestor dir") => {
        tracing::debug!("no ancestor dir for {prefix}: treating as empty listing");
        Vec::new()
    }
    Err(e) => return Err(e),
};

Prevention

When it happens

Trigger: list_files on a prefix whose top-level directory was never created (a tenant dir before its first upload); listing against a fresh/empty storage root; a storage_root configuration pointing at a tree where the prefix's ancestors are absent.

Common situations: Listing immediately after environment creation before any files were written; wrong storage_root config in dev/test; prefixes deeper than anything ever written.

Related errors


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