{"record":{"id":"230f11953f701249","repo":"astrid-runtime/astrid","slug":"legacy-state-source-is-redirected-or-not-a-directo","errorCode":null,"errorMessage":"legacy state source is redirected or not a directory: {}","messagePattern":"legacy state source is redirected or not a directory: (.+?)","errorType":"exception","errorClass":"io::Error","httpStatus":null,"severity":"error","filePath":"crates/astrid-core/src/dirs_layout_retirement.rs","lineNumber":17,"sourceCode":"//! No-follow retirement of the released directory-backed state tree.\n\n#[cfg(unix)]\nuse std::fs::File;\nuse std::io;\nuse std::path::Path;\n#[cfg(target_os = \"linux\")]\nuse std::path::PathBuf;\n\npub(super) fn validate_legacy_retirement_candidate(path: &Path) -> io::Result<()> {\n    let metadata = match std::fs::symlink_metadata(path) {\n        Ok(metadata) => metadata,\n        Err(error) if error.kind() == io::ErrorKind::NotFound => return Ok(()),\n        Err(error) => return Err(error),\n    };\n    if metadata.file_type().is_symlink() || !metadata.is_dir() {\n        return Err(io::Error::new(\n            io::ErrorKind::InvalidData,\n            format!(\n                \"legacy state source is redirected or not a directory: {}\",\n                path.display()\n            ),\n        ));\n    }\n    validate_legacy_tree(path, legacy_tree_device(&metadata))\n}\n\n#[cfg(unix)]\npub(super) fn legacy_tree_device(metadata: &std::fs::Metadata) -> u64 {\n    use std::os::unix::fs::MetadataExt as _;\n\n    metadata.dev()\n}\n\n#[cfg(not(unix))]","sourceCodeStart":1,"sourceCodeEnd":35,"githubUrl":"https://github.com/astrid-runtime/astrid/blob/affd8760f44190dbdfbec23403f4c4b642c33112/crates/astrid-core/src/dirs_layout_retirement.rs#L1-L35","documentation":"Before retiring the legacy directory-backed state tree, validate_legacy_retirement_candidate checks the configured legacy path with symlink_metadata: if it is a symlink (redirect) or not a real directory, the layout-v2 preflight refuses to proceed with InvalidData. This prevents migrating/retiring data that a symlink silently redirects elsewhere, or a path that never held the legacy state at all.","triggerScenarios":"Calling preflight_layout_v2_paths / validate_layout_v2_legacy_sources when the legacy state path either is a symlink or resolves to a non-directory (file, socket, etc.). A NotFound path is tolerated (no legacy state), but anything else non-directory fails.","commonSituations":"Dotfile managers (stow/dotbot) symlinked the state directory; the path was repointed to a file (e.g. a tarball or a moved database file); a config mistake points ASTRID at the wrong path; the state dir was replaced by a mount target that is not a directory.","solutions":["Remove the symlink and move the real state directory to the configured path (cp -a the target's contents, then delete the link).","Fix the configuration so the legacy state path points at the actual directory, not a file or a link.","Verify with: stat <path> — it must show a directory, not 'symbolic link'.","If there is genuinely no legacy state, ensure the path does not exist at all (the check passes on NotFound)."],"exampleFix":"// before\nln -s /mnt/data/astrid-state ~/.local/state/astrid\n// after\nrm ~/.local/state/astrid\nmv /mnt/data/astrid-state ~/.local/state/astrid   # real directory at configured path","handlingStrategy":"validation","validationCode":"use std::path::Path;\nfn legacy_path_ok(path: &Path) -> bool {\n    match std::fs::symlink_metadata(path) {\n        Ok(m) => !m.file_type().is_symlink() && m.is_dir(),\n        Err(e) if e.kind() == std::io::ErrorKind::NotFound => true, // no legacy state is fine\n        Err(_) => false,\n    }\n}","typeGuard":"fn is_plain_directory(p: &Path) -> bool {\n    std::fs::symlink_metadata(p).map(|m| !m.file_type().is_symlink() && m.is_dir()).unwrap_or(false)\n}","tryCatchPattern":"match result {\n    Err(e) if e.to_string().contains(\"redirected or not a directory\") => {\n        eprintln!(\"fix the configured legacy state path (no symlinks, must be a dir)\");\n        return Err(e);\n    }\n    other => other,\n}","preventionTips":["Never let dotfile managers symlink the state directory itself; manage the parent directory instead.","Before migration, run stat <legacy-path> and confirm it reports a directory, not a symbolic link.","If no legacy state exists, leave the path absent rather than pointing it at an unrelated file.","Keep the legacy state path stable across config changes; validate paths after editing configuration."],"tags":["symlink","filesystem","configuration","migration","layout"],"backgroundTag":"incompatible-source-type","analyzedSha":"affd8760f44190dbdfbec23403f4c4b642c33112","analyzedAt":"2026-09-09T21:28:12.402Z","contentChangedAt":"2026-09-09T21:28:12.402Z","schemaVersion":2},"datasetVersion":"2026-09-14T11:17:12.474Z"}