{"record":{"id":"1791bee1f285c9bf","repo":"astrid-runtime/astrid","slug":"scan-error","errorCode":null,"errorMessage":"scan {}: {error}","messagePattern":"scan (.+?): (.+?)","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"crates/astrid-kernel/src/principal_home_migration/unbound.rs","lineNumber":51,"sourceCode":"        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?);\n    }\n    for entry in entries {\n        admit_or_quarantine_entry(home, directory, identity, entry.path(), &entry.file_name())\n            .await?;\n    }\n    Ok(())\n}\n\nasync fn admit_or_quarantine_entry(\n    home: &AstridHome,\n    directory: &PrincipalDirectory,\n    identity: &dyn IdentityStore,\n    path: PathBuf,","sourceCodeStart":33,"sourceCodeEnd":69,"githubUrl":"https://github.com/astrid-runtime/astrid/blob/affd8760f44190dbdfbec23403f4c4b642c33112/crates/astrid-kernel/src/principal_home_migration/unbound.rs#L33-L69","documentation":"Thrown by admit_unbound_legacy_principal_homes when fs::read_dir on the legacy home root fails. The OS error kind is preserved (e.g. PermissionDenied, NotFound) and the scanned path is prefixed for context. This wraps the OS error so callers know which directory could not be enumerated during the unbound-leftover scan.","triggerScenarios":"admit_unbound_legacy_principal_homes calls read_dir on a source_root that exists and passed the private-directory-tree and no-redirects checks, but the OS cannot open it for reading — permissions changed between checks, the path was removed concurrently, or it sits on a failing mount.","commonSituations":"Read permission removed on the home directory (chmod/ACL/ownership change); the directory deleted by another process mid-scan; the path on an unmounted or disconnected filesystem.","solutions":["Fix permissions so the process can read the legacy home root (chown/chmod, restore ACLs).","Confirm the path exists on a mounted, healthy filesystem (df/mount, remount if needed).","Re-run the migration after any concurrent writers/deleters finish.","Run the migration as a user with access to the home tree rather than fighting ownership as root."],"exampleFix":"// before\n$ ls ~/.legacy-homes\nls: cannot open directory: Permission denied\n// after\n$ chmod 700 ~/.legacy-homes && chown $USER ~/.legacy-homes","handlingStrategy":"try-catch","validationCode":"fn source_root_readable(path: &Path) -> bool {\n    fs::read_dir(path).map(|_| true).unwrap_or(false)\n}","typeGuard":null,"tryCatchPattern":"match admit_unbound_legacy_principal_homes(&source_root, ...) {\n    Ok(()) => (),\n    Err(e) => {\n        eprintln!(\"scan failed for {}: {e}\", source_root.display());\n        if e.kind() == io::ErrorKind::PermissionDenied {\n            // fix permissions, then retry once\n            fix_home_permissions(&source_root)?;\n            admit_unbound_legacy_principal_homes(&source_root, ...)?;\n        } else { return Err(e); }\n    }\n}","preventionTips":["Run migration as the user who owns the home tree.","Verify read permissions and ownership on the legacy root before starting.","Avoid concurrent deleters/writers on the legacy home during scan.","Confirm the filesystem is mounted and healthy before migration."],"tags":["io","filesystem","permissions","migration"],"backgroundTag":"file-read-failed","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"}