astrid-runtime/astrid · error
scan {}: {error}
Error message
scan {}: {error} What it means
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.
Source
Thrown at crates/astrid-kernel/src/principal_home_migration/unbound.rs:51
Ok(metadata) => metadata,
Err(error) if error.kind() == io::ErrorKind::NotFound => return Ok(()),
Err(error) => return Err(error),
};
if metadata.file_type().is_symlink() || !metadata.is_dir() {
return Err(io::Error::new(
io::ErrorKind::InvalidData,
format!(
"legacy principal home root is not a regular directory: {}",
source_root.display()
),
));
}
astrid_core::platform_fs::ensure_private_directory_tree(&source_root)?;
astrid_core::platform_fs::verify_no_redirects(&source_root)?;
let mut entries = Vec::new();
for entry in fs::read_dir(&source_root).map_err(|error| {
io::Error::new(
error.kind(),
format!("scan {}: {error}", source_root.display()),
)
})? {
entries.push(entry?);
}
for entry in entries {
admit_or_quarantine_entry(home, directory, identity, entry.path(), &entry.file_name())
.await?;
}
Ok(())
}
async fn admit_or_quarantine_entry(
home: &AstridHome,
directory: &PrincipalDirectory,
identity: &dyn IdentityStore,
path: PathBuf,View on GitHub (pinned to affd8760f4)
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.
Example fix
// before $ ls ~/.legacy-homes ls: cannot open directory: Permission denied // after $ chmod 700 ~/.legacy-homes && chown $USER ~/.legacy-homes
Defensive patterns
Strategy: try-catch
Validate before calling
fn source_root_readable(path: &Path) -> bool {
fs::read_dir(path).map(|_| true).unwrap_or(false)
} Try / catch
match admit_unbound_legacy_principal_homes(&source_root, ...) {
Ok(()) => (),
Err(e) => {
eprintln!("scan failed for {}: {e}", source_root.display());
if e.kind() == io::ErrorKind::PermissionDenied {
// fix permissions, then retry once
fix_home_permissions(&source_root)?;
admit_unbound_legacy_principal_homes(&source_root, ...)?;
} else { return Err(e); }
}
} Prevention
- 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.
When it happens
Trigger: 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.
Common situations: 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.
Understand the failure class
Background: "failed to read file", EACCES, ENOENT and "could not read <path>" errors: when a program can't read a file from disk — this error's family across 49 libraries.
Related errors
- inspect legacy revocation file: {error}
- layout migration destination is redirected or not a regular
- layout migration receipt destination path does not match the
- layout migration source is redirected or not a directory: {}
- layout migration destination changed type: {}
AI-assisted analysis of astrid-runtime/astrid@affd8760f4 (2026-09-09).
Data as JSON: /api/errors/1791bee1f285c9bf.
Report an issue: GitHub.