astrid-runtime/astrid · error

legacy principal-home root is not a directory

Error message

legacy principal-home root is not a directory: {root}

What it means

A legacy-layout preflight checks that the principal-home root (`home.home_dir()`) is a plain directory. If `symlink_metadata` shows it is a symlink or not a directory, the check fails with InvalidData: legacy homes must be real directories so that redirects/symlinks cannot smuggle secrets or layout state outside the audited tree.

Solutions

  1. Replace the symlink with a real directory (move the target's contents into the path and delete the link).
  2. Run the tool with HOME / AstridHome pointing at a real directory, not a symlinked one.
  3. If a file occupies the path, move it aside and create the directory.
  4. Re-run the legacy audit after the path is a plain directory.

Example fix

// before: home path is a symlink
~/.astrid -> /mnt/data/astrid
// after
mv ~/.astrid ~/astrid-real && rm ~/.astrid
mv ~/astrid-real ~/.astrid  # real directory, no symlink
Defensive patterns

Strategy: validation

Validate before calling

let root = home.home_dir();
let md = std::fs::symlink_metadata(&root)?;
if md.file_type().is_symlink() || !md.is_dir() {
    return Err(format!("{} must be a real directory, not a symlink/file", root.display()));
}

Type guard

fn is_plain_dir(p: &std::path::Path) -> bool {
    match std::fs::symlink_metadata(p) {
        Ok(md) => !md.file_type().is_symlink() && md.is_dir(),
        Err(_) => false,
    }
}

Try / catch

match run_legacy_home_audit(&home) {
    Err(e) if e.kind() == std::io::ErrorKind::InvalidData && e.to_string().contains("not a directory") => {
        eprintln!("replace the symlink with a real directory, then retry");
    }
    other => other?,
}

Prevention

When it happens

Trigger: Running the legacy principal-home audit when the home path is a symlink (e.g. `$HOME` resolved through a symlinked dotdir) or a file/mount point rather than a directory.

Common situations: Dotfile managers replacing config directories with symlinks (chezmoi, stow, dotbot); home on a symlinked path; a stray file occupying the home path after a bad restore.

Related errors


AI-assisted analysis of astrid-runtime/astrid@affd8760f4 (2026-09-09). Data as JSON: /api/errors/891717d904188766. Report an issue: GitHub.

Appendix: source

Thrown at crates/astrid-kernel/src/lib.rs:4123

    })?;
    Ok(Arc::new(audit_log))
}

/// Enforce the released audit-source contract before opening any native
/// database. The first released layout had one system audit directory under
/// the `default` principal; ordinary principal-home migration deliberately
/// excludes every `.local/audit` subtree. An additional non-default source is
/// therefore a hard migration conflict rather than something that may be
/// silently left mounted or copied as ordinary home data.
#[cfg(unix)]
pub(crate) fn preflight_legacy_audit_sources(
    home: &astrid_core::dirs::AstridHome,
    default_source: &Path,
) -> std::io::Result<bool> {
    let root = home.home_dir();
    let metadata = match std::fs::symlink_metadata(&root) {
        Ok(metadata) if metadata.file_type().is_symlink() || !metadata.is_dir() => {
            return Err(std::io::Error::new(
                std::io::ErrorKind::InvalidData,
                format!(
                    "legacy principal-home root is not a directory: {}",
                    root.display()
                ),
            ));
        },
        Ok(metadata) => metadata,
        Err(error) if error.kind() == std::io::ErrorKind::NotFound => return Ok(false),
        Err(error) => return Err(error),
    };
    let root_device = audit_tree_device(&metadata);
    let mut default_source_present = false;
    astrid_core::platform_fs::verify_no_redirects(&root)?;
    for entry in std::fs::read_dir(&root)? {
        let entry = entry?;
        let principal_root = entry.path();
        let principal_metadata = std::fs::symlink_metadata(&principal_root)?;

View on GitHub (pinned to affd8760f4)