astrid-runtime/astrid · error

legacy secrets root is not a regular directory

Error message

legacy secrets root is not a regular directory: {}

What it means

`ensure_legacy_secret_aliases` validates the legacy secrets root before creating aliases. If the root exists but is a symlink or not a directory, the function fails with `InvalidData` rather than aliasing into an unverified location.

Solutions

  1. Replace the symlink/file with a real directory containing the legacy secret files.
  2. Recreate the legacy secrets root at its expected path and re-run migration.
  3. Exclude the secrets root from symlink-managing tools.

Example fix

# before
secrets -> ~/dotfiles/secrets
# after
rm secrets && mkdir secrets && cp -L ~/dotfiles/secrets/* secrets/
Defensive patterns

Strategy: validation

Validate before calling

fn secrets_root_ok(root: &std::path::Path) -> bool {
    match std::fs::symlink_metadata(root) {
        Ok(m) => m.is_dir() && !m.file_type().is_symlink(),
        Err(e) => e.kind() == std::io::ErrorKind::NotFound,
    }
}

Type guard

fn is_real_dir_or_missing(p: &std::path::Path) -> bool {
    !std::path::Path::new(p).symlink_metadata().map(|m| m.file_type().is_symlink() || !m.is_dir()).unwrap_or(false)
}

Try / catch

match ensure_legacy_secret_aliases(root, ...) {
    Err(e) if e.kind() == std::io::ErrorKind::InvalidData
        && e.to_string().contains("not a regular directory") => {
        eprintln!("recreate the legacy secrets root as a real directory");
        return Err(e.into());
    }
    other => other,
}

Prevention

When it happens

Trigger: Calling `ensure_legacy_secret_aliases` when `symlink_metadata(root)` succeeds but `is_symlink()` is true or `is_dir()` is false.

Common situations: The whole legacy secrets directory was symlinked (dotfiles manager, backup restore creating symlinks); a file overwrote the secrets root path.

Understand the failure class

Background: "is not a compatible type" / "cannot merge" errors: when a value's type doesn't match what the library requires — this error's family across 65 libraries.

Related errors


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

Appendix: source

Thrown at crates/astrid-kernel/src/legacy_migration_barrier/host_fs.rs:89

    fs::remove_dir(path).map_err(io::Error::other)?;
    sync_parent(path)
}

/// Check every alias-keyed child of the released `secrets/` root.  The
/// barrier passes `allow_empty_cleanup=false` while resuming a completed
/// ledger, so a deleted or renamed principal cannot leave a reappeared empty
/// directory that is silently swept on restart.
pub(super) fn ensure_legacy_secret_aliases(
    root: &Path,
    allow_empty_cleanup: bool,
) -> io::Result<()> {
    let metadata = match fs::symlink_metadata(root) {
        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 secrets root is not a regular directory: {}",
                root.display()
            ),
        ));
    }
    astrid_core::platform_fs::validate_private_directory(root)?;
    astrid_core::platform_fs::verify_no_redirects(root)?;
    let entries = fs::read_dir(root)
        .map_err(io::Error::other)?
        .collect::<Result<Vec<_>, _>>()
        .map_err(io::Error::other)?;
    for entry in entries {
        if entry.file_name() == "__host__" {
            continue;
        }
        let path = entry.path();

View on GitHub (pinned to affd8760f4)