{"record":{"id":"a0abad66b7d17edd","repo":"astrid-runtime/astrid","slug":"legacy-state-source-is-not-a-directory","errorCode":null,"errorMessage":"legacy state source is not a directory: {}","messagePattern":"legacy state source is not a directory: (.+?)","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"crates/astrid-core/src/dirs_layout_retirement.rs","lineNumber":49,"sourceCode":"\n    metadata.dev()\n}\n\n#[cfg(not(unix))]\npub(super) fn legacy_tree_device(_metadata: &std::fs::Metadata) -> u64 {\n    0\n}\n\npub(super) fn validate_legacy_tree(path: &Path, root_device: u64) -> io::Result<()> {\n    let metadata = std::fs::symlink_metadata(path)?;\n    if metadata.file_type().is_symlink() {\n        return Err(io::Error::new(\n            io::ErrorKind::InvalidData,\n            format!(\"legacy state source is redirected: {}\", path.display()),\n        ));\n    }\n    if !metadata.is_dir() {\n        return Err(io::Error::new(\n            io::ErrorKind::InvalidData,\n            format!(\"legacy state source is not a directory: {}\", path.display()),\n        ));\n    }\n    crate::platform_fs::verify_no_redirects(path)?;\n    ensure_legacy_tree_boundary(path, root_device, &metadata)?;\n\n    for entry in std::fs::read_dir(path)? {\n        let entry = entry?;\n        let child = entry.path();\n        let child_metadata = std::fs::symlink_metadata(&child)?;\n        if child_metadata.file_type().is_symlink() {\n            return Err(io::Error::new(\n                io::ErrorKind::InvalidData,\n                format!(\n                    \"legacy state source contains a redirect: {}\",\n                    child.display()\n                ),","sourceCodeStart":31,"sourceCodeEnd":67,"githubUrl":"https://github.com/astrid-runtime/astrid/blob/affd8760f44190dbdfbec23403f4c4b642c33112/crates/astrid-core/src/dirs_layout_retirement.rs#L31-L67","documentation":"Thrown by validate_legacy_tree when the legacy state source path exists but is a regular file, FIFO, or other non-directory entry instead of the expected directory. The retirement machinery only knows how to validate and delete directory trees, so it refuses with InvalidData rather than proceeding.","triggerScenarios":"validate_legacy_tree (via validate_legacy_retirement_candidate or retire_legacy_source_tree) sees symlink_metadata report is_dir() == false for the source path. Occurs if the state path was replaced by a plain file, a truncated restore left a file where the directory was, or a race changed the entry type after the caller's initial check.","commonSituations":"Backup restores that recreated state as a tar file left in place; scripts that wrote a marker file at the state path; migration tooling that deleted the directory and wrote a placeholder; concurrent processes swapping the path mid-retirement.","solutions":["Inspect the path (ls -la / stat) and determine what the non-directory entry is","Remove or rename the stray file and restore the real state directory contents there","Re-run retirement after the path is a real directory again","Check for concurrent writers (another astrid instance, migration job) that may be swapping the entry"],"exampleFix":"// before: state path holds a stray file\n/srv/astrid/var/state  (regular file, 42 bytes)\n// after: remove the stray file and restore the directory\nmv /srv/astrid/var/state /srv/astrid/var/state.stray\nmkdir -p /srv/astrid/var/state   # restore contents from backup","handlingStrategy":"validation","validationCode":"fn ensure_state_dir(path: &Path) -> std::io::Result<()> {\n    let meta = std::fs::symlink_metadata(path)?;\n    if !meta.is_dir() {\n        return Err(std::io::Error::new(\n            std::io::ErrorKind::InvalidData,\n            format!(\"{} must be a real directory\", path.display()),\n        ));\n    }\n    Ok(())\n}","typeGuard":"fn is_plain_directory(path: &Path) -> bool {\n    std::fs::symlink_metadata(path)\n        .map(|m| m.is_dir())\n        .unwrap_or(false)\n}","tryCatchPattern":"match retire_legacy_source_tree(&path) {\n    Err(e) if e.kind() == std::io::ErrorKind::InvalidData && e.to_string().contains(\"not a directory\") => {\n        // inspect entry, restore directory contents, retry\n    }\n    other => other?,\n}","preventionTips":["Verify the state path is a directory after every restore or migration","Don't write placeholder/marker files into configured state paths","Ensure only one process owns lifecycle operations on the state tree"],"tags":["rust","io","filesystem","path"],"backgroundTag":"path-is-not-a-directory","analyzedSha":"affd8760f44190dbdfbec23403f4c4b642c33112","analyzedAt":"2026-09-09T21:28:12.402Z","contentChangedAt":"2026-09-09T21:28:12.402Z","schemaVersion":2},"datasetVersion":"2026-09-14T05:17:10.506Z"}