{"record":{"id":"16968c3cd1c0e8bc","repo":"astrid-runtime/astrid","slug":"legacy-principal-home-entry-crosses-a-filesystem-b-16968c","errorCode":null,"errorMessage":"legacy principal-home entry crosses a filesystem boundary: {principal_root}","messagePattern":"legacy principal-home entry crosses a filesystem boundary: (.+?)","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"crates/astrid-kernel/src/lib.rs","lineNumber":4152,"sourceCode":"    };\n    let root_device = audit_tree_device(&metadata);\n    let mut default_source_present = false;\n    astrid_core::platform_fs::verify_no_redirects(&root)?;\n    for entry in std::fs::read_dir(&root)? {\n        let entry = entry?;\n        let principal_root = entry.path();\n        let principal_metadata = std::fs::symlink_metadata(&principal_root)?;\n        if principal_metadata.file_type().is_symlink() || !principal_metadata.is_dir() {\n            return Err(std::io::Error::new(\n                std::io::ErrorKind::InvalidData,\n                format!(\n                    \"legacy principal-home entry is not a regular directory: {}\",\n                    principal_root.display()\n                ),\n            ));\n        }\n        if audit_tree_device(&principal_metadata) != root_device {\n            return Err(std::io::Error::new(\n                std::io::ErrorKind::InvalidData,\n                format!(\n                    \"legacy principal-home entry crosses a filesystem boundary: {}\",\n                    principal_root.display()\n                ),\n            ));\n        }\n        astrid_core::platform_fs::verify_no_redirects(&principal_root)?;\n        let local_root = principal_root.join(\".local\");\n        match std::fs::symlink_metadata(&local_root) {\n            Ok(metadata) if metadata.file_type().is_symlink() || !metadata.is_dir() => {\n                return Err(std::io::Error::new(\n                    std::io::ErrorKind::InvalidData,\n                    format!(\n                        \"legacy principal .local path is not a directory: {}\",\n                        local_root.display()\n                    ),\n                ));","sourceCodeStart":4134,"sourceCodeEnd":4170,"githubUrl":"https://github.com/astrid-runtime/astrid/blob/affd8760f44190dbdfbec23403f4c4b642c33112/crates/astrid-kernel/src/lib.rs#L4134-L4170","documentation":"During the legacy principal-home audit migration, the kernel verifies that the principal root and everything under it (via audit_tree_device/st_dev comparison) lives on a single filesystem. If the legacy principal-home entry's device ID differs from the root filesystem's device, the rename-based migration could silently lose the atomicity and write-through guarantees it relies on, so the operation is refused with InvalidData.","triggerScenarios":"Calling the legacy principal-home migration path when the principal_root directory resides on a different mount point (different st_dev) than the filesystem root — e.g. the home directory is a separate mount, bind mount, or network share.","commonSituations":"Users whose home directories are automounted NFS volumes, separate /home partitions, or bind-mounted directories; containers where the principal root is a volume mount while the parent is on the container's overlay filesystem.","solutions":["Move the principal-home directory onto the same filesystem/mount as the root, then re-run the migration","Remove any bind mounts or symlinks-to-other-mounts that place principal_root on another device","If the split is intentional, migrate the audit data manually into migrations_dir/audit-principal-home.retired so the automatic migration is skipped"],"exampleFix":"// before: principal root on a separate mount\n/home/alice/.app/principal  -> mounted tmpfs volume\n// after: keep the whole tree on one filesystem\n/home/alice/.app/principal  (regular directory on the same device as root)","handlingStrategy":"validation","validationCode":"use std::os::unix::fs::MetadataExt;\nfn same_device(a: &std::path::Path, b: &std::path::Path) -> std::io::Result<bool> {\n    Ok(a.symlink_metadata()?.dev() == b.symlink_metadata()?.dev())\n}\n// call before migration:\n// assert!(same_device(principal_root, &root)?)","typeGuard":"fn is_real_dir(p: &std::path::Path) -> bool {\n    std::fs::symlink_metadata(p).map(|m| m.is_dir()).unwrap_or(false)\n}","tryCatchPattern":"match migrate_legacy_principal_home() {\n    Err(e) if e.kind() == std::io::ErrorKind::InvalidData\n        && e.to_string().contains(\"filesystem boundary\") => {\n        eprintln!(\"move principal root onto one filesystem, then retry: {e}\");\n    }\n    Err(e) => return Err(e),\n    Ok(()) => {}\n}","preventionTips":["Keep the entire principal home on one filesystem; avoid bind mounts inside it","Check `stat -c %d <path>` on the principal root and its parent before upgrading","Document mount layout for machines running the migration"],"tags":["filesystem","mount-boundary","migration","rust"],"backgroundTag":"cross-filesystem-boundary","analyzedSha":"affd8760f44190dbdfbec23403f4c4b642c33112","analyzedAt":"2026-09-09T21:28:12.402Z","contentChangedAt":"2026-09-09T21:28:12.402Z","schemaVersion":2},"datasetVersion":"2026-09-17T15:17:12.973Z"}