{"record":{"id":"86be9a7686a1e692","repo":"astrid-runtime/astrid","slug":"layout-migration-source-contains-a-redirect","errorCode":null,"errorMessage":"layout migration source contains a redirect: {}","messagePattern":"layout migration source contains a redirect: (.+?)","errorType":"exception","errorClass":"io::Error","httpStatus":null,"severity":"error","filePath":"crates/astrid-core/src/dirs_layout_records.rs","lineNumber":374,"sourceCode":"    })\n}\n\nfn inventory_directory(\n    root: &Path,\n    directory: &Path,\n    hasher: &mut blake3::Hasher,\n    entries: &mut u64,\n    bytes: &mut u64,\n) -> io::Result<()> {\n    let mut children = std::fs::read_dir(directory)?.collect::<Result<Vec<_>, _>>()?;\n    children.sort_by_key(std::fs::DirEntry::file_name);\n    for child in children {\n        let child_path = child.path();\n        let relative = child_path.strip_prefix(root).map_err(io::Error::other)?;\n        let relative_bytes = relative.as_os_str().as_encoded_bytes();\n        let metadata = std::fs::symlink_metadata(&child_path)?;\n        if metadata.file_type().is_symlink() {\n            return Err(io::Error::new(\n                io::ErrorKind::InvalidData,\n                format!(\n                    \"layout migration source contains a redirect: {}\",\n                    child_path.display()\n                ),\n            ));\n        }\n        super::retirement::validate_legacy_surrealkv_entry(\n            relative,\n            metadata.is_dir(),\n            metadata.is_file(),\n        )?;\n        *entries = entries\n            .checked_add(1)\n            .ok_or_else(|| io::Error::other(\"layout inventory entry count overflow\"))?;\n        hash_inventory_field(hasher, b\"path\", relative_bytes);\n        if metadata.is_dir() {\n            hasher.update(b\"directory\");","sourceCodeStart":356,"sourceCodeEnd":392,"githubUrl":"https://github.com/astrid-runtime/astrid/blob/affd8760f44190dbdfbec23403f4c4b642c33112/crates/astrid-core/src/dirs_layout_records.rs#L356-L392","documentation":"During a layout-v2 migration, the inventory walk (inventory_directory, driven by inventory_tree) found a symlink inside the legacy state directory it is fingerprinting. The migration refuses to hash trees containing redirects because a symlink could change what data the inventory digest covers and could escape the state directory boundary. The operation aborts with io::ErrorKind::InvalidData naming the offending path.","triggerScenarios":"Running a layout v2 migration (inventory_tree/inventory_directory) while any child entry of the source directory — at any depth — is a symlink (symlink_metadata reports is_symlink).","commonSituations":"Users symlink state directories into dotfiles managers (e.g. stow, GNU stow/dotbot) or to another disk; a backup/restore tool replaced a subdirectory with a symlink; someone hand-linked a file from elsewhere into the state tree.","solutions":["Find the symlink named in the message and remove it, copying real data in its place (rm the link, cp -L the target contents).","Re-point your dotfile/symlink manager at the state directory as a whole rather than entries inside it.","If the whole state directory should live elsewhere, migrate the entire directory (no symlinks inside) and ensure the configured path itself points to the real location.","Re-run the migration once the tree contains only real files and directories."],"exampleFix":"// before: state dir contains a symlink\nln -s /mnt/fast/surrealkv ~/.local/state/astrid/surrealkv\n// after: real directory, link placed one level up\nmv ~/real/astrid-state ~/.local/state/astrid  # move real data\nrm ~/.local/state/astrid/surrealkv            # remove the symlink first","handlingStrategy":"validation","validationCode":"use std::path::Path;\nfn assert_no_symlinks(dir: &Path) -> std::io::Result<()> {\n    for entry in std::fs::read_dir(dir)? {\n        let entry = entry?;\n        let meta = std::fs::symlink_metadata(entry.path())?;\n        if meta.file_type().is_symlink() {\n            return Err(std::io::Error::new(\n                std::io::ErrorKind::InvalidData,\n                format!(\"symlink in state tree: {}\", entry.path().display()),\n            ));\n        }\n        if meta.is_dir() {\n            assert_no_symlinks(&entry.path())?;\n        }\n    }\n    Ok(())\n}","typeGuard":"fn is_real_directory(p: &Path) -> bool {\n    std::fs::symlink_metadata(p).map(|m| m.is_dir()).unwrap_or(false)\n}","tryCatchPattern":null,"preventionTips":["Configure dotfile managers to manage the parent directory, not entries inside the state tree.","Audit the state tree before migrating: find <state-dir> -type l.","Keep the state directory on the filesystem where it was created; use mounts at the top level, not symlinks inside."],"tags":["symlink","filesystem","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"}