astrid-runtime/astrid · error
legacy principal .local path is not a directory
Error message
legacy principal .local path is not a directory: {} What it means
For each principal directory, preflight_legacy_audit_sources checks the .local subpath: if it exists but is a symlink or a non-directory, the migration aborts with InvalidData. Following a redirected .local could let the importer operate on audit data outside the principal's own tree, so redirects are rejected outright (absence is fine and is skipped).
Solutions
- Replace the .local symlink/file with a real directory (copy the linked contents into place).
- Remove the stray file occupying the .local path and recreate it as a directory with private permissions.
- Reconfigure the redirection tool (dotfile manager, XDG env setup) so .local stays a physical directory.
- If .local is not needed, remove the entry entirely — absence is accepted by the preflight.
Example fix
// before .local -> /dotfiles/local // after $ rm .local && mkdir -m 700 .local && cp -a /dotfiles/local/. .local/
Defensive patterns
Strategy: validation
Validate before calling
fn local_path_ok(principal_root: &std::path::Path) -> std::io::Result<bool> {
let local = principal_root.join(".local");
match std::fs::symlink_metadata(&local) {
Ok(m) => Ok(!m.file_type().is_symlink() && m.is_dir()),
Err(e) if e.kind() == std::io::ErrorKind::NotFound => Ok(true), // tolerated
Err(e) => Err(e),
}
} Type guard
fn is_plain_dir_or_missing(p: &std::path::Path) -> bool {
match std::fs::symlink_metadata(p) {
Ok(m) => !m.file_type().is_symlink() && m.is_dir(),
Err(e) => e.kind() == std::io::ErrorKind::NotFound,
}
} Try / catch
match result {
Err(e) if e.kind() == std::io::ErrorKind::InvalidData && e.to_string().contains(".local path is not a directory") => {
// replace the symlink/file at .local with a real directory, then retry
}
other => other?,
} Prevention
- Keep .local as a real directory under each principal root.
- Audit with find <principal-root> -name .local -type l before migrating.
- Configure XDG/dotfile tooling not to replace .local with a symlink.
When it happens
Trigger: migrate_legacy_audit with a principal dir containing <principal>/.local as a symlink, regular file, or special file (host_fs.rs:401). A NotFound .local is allowed and simply skipped via continue.
Common situations: Symlinking .local to a shared or dotfiles-managed location; a file named .local created by accident; XDG-style redirection tooling replacing the directory with a link.
Related errors
- InvalidData
- layout migration destination is redirected or not a regular…
- layout migration source contains a redirect
- layout migration source is redirected or not a directory
- legacy audit source is not a regular directory
AI-assisted analysis of astrid-runtime/astrid@affd8760f4 (2026-09-09).
Data as JSON: /api/errors/53bd1c3f18b54059.
Report an issue: GitHub.
Appendix: source
Thrown at crates/astrid-kernel/src/legacy_migration_barrier/host_fs.rs:402
"legacy principal-home entry is not a regular directory: {}",
principal_root.display()
),
));
}
if device_id(&principal_metadata) != root_device {
return Err(io::Error::new(
io::ErrorKind::InvalidData,
format!(
"legacy principal-home entry crosses a filesystem boundary: {}",
principal_root.display()
),
));
}
astrid_core::platform_fs::verify_no_redirects(&principal_root)?;
let local_root = principal_root.join(".local");
match fs::symlink_metadata(&local_root) {
Ok(metadata) if metadata.file_type().is_symlink() || !metadata.is_dir() => {
return Err(io::Error::new(
io::ErrorKind::InvalidData,
format!(
"legacy principal .local path is not a directory: {}",
local_root.display()
),
));
},
Ok(_) => astrid_core::platform_fs::verify_no_redirects(&local_root)?,
Err(error) if error.kind() == io::ErrorKind::NotFound => continue,
Err(error) => return Err(error),
}
let audit_source = local_root.join("audit");
let audit_metadata = match fs::symlink_metadata(&audit_source) {
Ok(metadata) => metadata,
Err(error) if error.kind() == io::ErrorKind::NotFound => continue,
Err(error) => return Err(error),
};
if audit_source == default_source {View on GitHub (pinned to affd8760f4)