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

  1. Replace the .local symlink/file with a real directory (copy the linked contents into place).
  2. Remove the stray file occupying the .local path and recreate it as a directory with private permissions.
  3. Reconfigure the redirection tool (dotfile manager, XDG env setup) so .local stays a physical directory.
  4. 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

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


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)