{"record":{"id":"18ec6b0162fbc698","repo":"astrid-runtime/astrid","slug":"layout-path-is-redirected-or-not-a-directory","errorCode":null,"errorMessage":"layout path is redirected or not a directory: {}","messagePattern":"layout path is redirected or not a directory: (.+?)","errorType":"exception","errorClass":"io::Error","httpStatus":null,"severity":"error","filePath":"crates/astrid-core/src/dirs_layout.rs","lineNumber":426,"sourceCode":"                        &self.storage_volume_path(),\n                    )?;\n                    retire_legacy_source_tree(&self.state_db_path())\n                }\n            },\n        }\n    }\n\n    fn ensure_private_dir(path: &Path) -> io::Result<()> {\n        crate::platform_fs::ensure_private_directory(path)\n    }\n}\n\nfn verify_existing_ancestor(path: &Path) -> io::Result<()> {\n    let mut candidate = path;\n    loop {\n        match std::fs::symlink_metadata(candidate) {\n            Ok(metadata) if metadata.file_type().is_symlink() || !metadata.is_dir() => {\n                return Err(io::Error::new(\n                    io::ErrorKind::InvalidData,\n                    format!(\n                        \"layout path is redirected or not a directory: {}\",\n                        candidate.display()\n                    ),\n                ));\n            },\n            Ok(_) => return crate::platform_fs::verify_no_redirects(candidate),\n            Err(error) if error.kind() == io::ErrorKind::NotFound => {\n                candidate = candidate.parent().ok_or_else(|| {\n                    io::Error::new(\n                        io::ErrorKind::InvalidData,\n                        format!(\n                            \"layout path has no existing directory ancestor: {}\",\n                            path.display()\n                        ),\n                    )\n                })?;","sourceCodeStart":408,"sourceCodeEnd":444,"githubUrl":"https://github.com/astrid-runtime/astrid/blob/affd8760f44190dbdfbec23403f4c4b642c33112/crates/astrid-core/src/dirs_layout.rs#L408-L444","documentation":"verify_existing_ancestor, part of the preflight_layout_v2_paths check run by the migration APIs, walks each home path (root, var/, migrations/, principal-store, content staging, cow/, state.db) looking for the nearest existing ancestor. If that ancestor is a symlink or not a directory, migration is refused with InvalidData. This is a no-follow guard against redirected home layouts, where part of the path could silently point at another location or volume.","triggerScenarios":"Calling begin_layout_v2_migration or complete_layout_v2 when any component of the Astrid home paths (e.g. var, var/migrations, state.db's parent chain) resolves through a symlink or an existing non-directory entry (a regular file where a directory is expected).","commonSituations":"Symlinking ~/.astrid/var to another disk; a stray regular file named 'var' inside the home; overlay/restore tooling substituting directories with links; moving part of the home with ln -s to save space.","solutions":["Run ls -la on each path component of the reported path and replace any symlink with a real directory (move the data, remove the link, copy/move contents back)","Remove or rename any non-directory file occupying a path that must be a directory (e.g. a file named var)","Keep the entire Astrid home on real directories; use Astrid's own volume/storage mechanisms rather than symlinks for relocation","If the redirect is intentional (e.g. home on another volume), relocate the whole home root instead of individual subdirectories"],"exampleFix":"// before: var is a symlink to /mnt/big/astrid-var\nln -s /mnt/big/astrid-var ~/.astrid/var\nhome.begin_layout_v2_migration(&target)?; // InvalidData: redirected path\n// after\nmv ~/.astrid/var /mnt/big/astrid-var.bak && rm ~/.astrid/var\nmv /mnt/big/astrid-var.bak ~/.astrid/var  # real directory, no symlink","handlingStrategy":"validation","validationCode":"for p in [root.join(\"var\"), root.join(\"var/migrations\"), root.join(\"etc\")] {\n    let md = std::fs::symlink_metadata(&p)?;\n    if md.file_type().is_symlink() || !md.is_dir() {\n        return Err(anyhow!(\"{} is a symlink or not a directory\", p.display()));\n    }\n}","typeGuard":"fn is_real_directory(p: &Path) -> bool {\n    std::fs::symlink_metadata(p).map(|m| m.is_dir() && !m.file_type().is_symlink()).unwrap_or(false)\n}","tryCatchPattern":"match home.begin_layout_v2_migration(&target) {\n    Err(e) if e.to_string().contains(\"redirected or not a directory\") => {\n        // replace the symlink with a real directory before retrying\n    },\n    r => r?,\n}","preventionTips":["Never symlink subdirectories of the Astrid home; move the entire home instead","Audit for symlinks after restores or disk migrations: find <home> -type l","Let the library create its own directory skeleton via ensure","Use volume/storage features provided by the library for relocation needs"],"tags":["io","security","symlink","path-validation"],"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"}