{"record":{"id":"6f5030b7666377e0","repo":"neondatabase/neon","slug":"list-files-failed-to-find-valid-ancestor-dir-for","errorCode":null,"errorMessage":"list_files: failed to find valid ancestor dir for {full_path}","messagePattern":"list_files: failed to find valid ancestor dir for (.+?)","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"warning","filePath":"libs/remote_storage/src/local_fs.rs","lineNumber":168,"sourceCode":"        };\n\n        // If we were given a directory, we may use it as our starting point.\n        // Otherwise, we must go up to the first ancestor dir that exists.  This is because\n        // S3 object list prefixes can be arbitrary strings, but when reading\n        // the local filesystem we need a directory to start calling read_dir on.\n        let mut initial_dir = full_path.clone();\n\n        // If there's no trailing slash, we have to start looking from one above: even if\n        // `initial_dir` is a directory, we should still list any prefixes in the parent\n        // that start with the same string.\n        if !full_path.to_string().ends_with('/') {\n            initial_dir.pop();\n        }\n\n        loop {\n            // Did we make it to the root?\n            if initial_dir.parent().is_none() {\n                anyhow::bail!(\"list_files: failed to find valid ancestor dir for {full_path}\");\n            }\n\n            match fs::metadata(initial_dir.clone()).await {\n                Ok(meta) if meta.is_dir() => {\n                    // We found a directory, break\n                    break;\n                }\n                Ok(_meta) => {\n                    // It's not a directory: strip back to the parent\n                    initial_dir.pop();\n                }\n                Err(e) if e.kind() == ErrorKind::NotFound => {\n                    // It's not a file that exists: strip the prefix back to the parent directory\n                    initial_dir.pop();\n                }\n                Err(e) => {\n                    // Unexpected I/O error\n                    anyhow::bail!(e)","sourceCodeStart":150,"sourceCodeEnd":186,"githubUrl":"https://github.com/neondatabase/neon/blob/8f60b04da47ffefe0e52bda2440134b42874eb75/libs/remote_storage/src/local_fs.rs#L150-L186","documentation":"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.","triggerScenarios":"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.","commonSituations":"Listing immediately after environment creation before any files were written; wrong storage_root config in dev/test; prefixes deeper than anything ever written.","solutions":["Treat this error as an empty listing at the call site — if no ancestor directory exists, no files can exist under the prefix","Ensure the directory tree is created before listing (the upload path creates dirs on demand)","Verify storage_root points at the intended, populated directory","If arbitrary-prefix listing must not fail, pre-create the top-level directory layout"],"exampleFix":"// before: empty tree turns into a hard error\nlet files = local_fs.list_files(Some(&prefix), None, &cancel).await?;\n\n// after: map 'no ancestor dir' to an empty result\nlet files = match local_fs.list_files(Some(&prefix), None, &cancel).await {\n    Ok(files) => files,\n    Err(e) if e.to_string().contains(\"failed to find valid ancestor dir\") => Vec::new(),\n    Err(e) => return Err(e),\n};","handlingStrategy":"validation","validationCode":"// If no ancestor of the prefix exists, the listing is definitionally empty — check first.\nasync fn first_existing_ancestor(storage_root: &Utf8Path, prefix: &RemotePath) -> Option<Utf8PathBuf> {\n    let mut dir = prefix.with_base(storage_root);\n    loop {\n        if tokio::fs::try_exists(&dir).await.ok()? {\n            return Some(dir);\n        }\n        if !dir.pop() { return None; }\n    }\n}\n// caller: if first_existing_ancestor(...).is_none() { return Ok(Vec::new()); }","typeGuard":null,"tryCatchPattern":"// Recognize the 'nothing exists yet' bail and map it to an empty listing.\nlet files = match local_fs.list_files(Some(&prefix), None, &cancel).await {\n    Ok(files) => files,\n    Err(e) if format!(\"{e:#}\").contains(\"failed to find valid ancestor dir\") => {\n        tracing::debug!(\"no ancestor dir for {prefix}: treating as empty listing\");\n        Vec::new()\n    }\n    Err(e) => return Err(e),\n};","preventionTips":["Create the top-level directory layout (e.g. tenant dirs) at environment setup time","Treat 'no ancestor exists' as an empty result, not an exceptional state, when porting S3-style prefixes to local fs","Verify storage_root configuration with a smoke listing at startup"],"tags":["local-filesystem","listing","directory","path","configuration"],"backgroundTag":"directory-not-found","analyzedSha":"8f60b04da47ffefe0e52bda2440134b42874eb75","analyzedAt":"2026-08-16T23:39:28.135Z","schemaVersion":2},"datasetVersion":"2026-08-17T04:17:16.089Z"}