{"record":{"id":"fc1f14aa25c7f9d6","repo":"astrid-runtime/astrid","slug":"legacy-principal-home-root-is-not-a-regular-direct","errorCode":null,"errorMessage":"legacy principal home root is not a regular directory: {}","messagePattern":"legacy principal home root is not a regular directory: (.+?)","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"crates/astrid-kernel/src/principal_home_migration/unbound.rs","lineNumber":38,"sourceCode":"const QUARANTINE_DIR: &str = \"unbound-legacy-homes\";\n\n/// Mint identities for leftover valid aliases and quarantine invalid names.\n///\n/// Call this only on the first layout-1 cut-over, before the barrier snapshots\n/// admitted bindings. Existing-v2 leftover sources still fail closed later.\npub(crate) async fn admit_unbound_legacy_principal_homes(\n    home: &AstridHome,\n    directory: &PrincipalDirectory,\n    identity: &dyn IdentityStore,\n) -> io::Result<()> {\n    let source_root = home.home_dir();\n    let metadata = match fs::symlink_metadata(&source_root) {\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 principal home root is not a regular directory: {}\",\n                source_root.display()\n            ),\n        ));\n    }\n    astrid_core::platform_fs::ensure_private_directory_tree(&source_root)?;\n    astrid_core::platform_fs::verify_no_redirects(&source_root)?;\n\n    let mut entries = Vec::new();\n    for entry in fs::read_dir(&source_root).map_err(|error| {\n        io::Error::new(\n            error.kind(),\n            format!(\"scan {}: {error}\", source_root.display()),\n        )\n    })? {\n        entries.push(entry?);","sourceCodeStart":20,"sourceCodeEnd":56,"githubUrl":"https://github.com/astrid-runtime/astrid/blob/affd8760f44190dbdfbec23403f4c4b642c33112/crates/astrid-kernel/src/principal_home_migration/unbound.rs#L20-L56","documentation":"Thrown by admit_unbound_legacy_principal_homes when the legacy principal home root exists but is not a regular directory — it is a symlink or another file type. Migration refuses to scan or adopt content from anything that could be a redirect or non-directory, to avoid following links outside the trusted tree. A NotFound error is deliberately tolerated (nothing to migrate); a wrong-type existing path is not.","triggerScenarios":"admit_unbound_legacy_principal_homes calls fs::symlink_metadata on the legacy home root and finds is_symlink() true or is_dir() false (regular file, FIFO, device node, etc.).","commonSituations":"The legacy home path was replaced by a symlink to relocate storage; the path points at a regular file due to a botched copy/restore; a container or mount maps the home path to a non-directory.","solutions":["Replace the symlink/non-directory at the legacy home path with a real directory containing the legacy principal homes.","Point migration's source_root configuration at the actual legacy directory rather than a link to it.","Check mount points and restores: ensure the expected directory structure exists at the configured path.","Remove any stale non-directory placeholder and re-run migration."],"exampleFix":"// before\nmv ~/.legacy-homes ~/.legacy-homes-real\nln -s ~/.legacy-homes-real ~/.legacy-homes  // symlink is rejected\n// after\nrm ~/.legacy-homes\nmv ~/.legacy-homes-real ~/.legacy-homes      // real directory","handlingStrategy":"validation","validationCode":"fn legacy_root_ok(path: &Path) -> bool {\n    match fs::symlink_metadata(path) {\n        Ok(m) => !m.file_type().is_symlink() && m.is_dir(),\n        Err(e) => e.kind() == io::ErrorKind::NotFound, // tolerated: nothing to migrate\n    }\n}","typeGuard":null,"tryCatchPattern":"match admit_unbound_legacy_principal_homes(&source_root, ...) {\n    Ok(()) => (),\n    Err(e) if e.to_string().contains(\"not a regular directory\") => {\n        // replace symlink/file with a real directory, then retry\n        return Err(e.into_config_error(\"fix legacy home root path\"));\n    }\n    Err(e) => return Err(e),\n}","preventionTips":["Ensure the legacy home root is a real directory, never a symlink.","Point configuration at actual directories, not links to them.","Check for symlinks in the migration path (find -type l) before starting.","After restores, verify directory structure and types match expectations."],"tags":["io","filesystem","symlink","security","migration"],"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"}