astrid-runtime/astrid · error

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

Error message

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

What it means

Thrown by admit_unbound_legacy_principal_homes when the legacy principal home root exists but is not a regular directory — it is a symlink or another file type. Migration refuses to scan or adopt content from anything that could be a redirect or non-directory, to avoid following links outside the trusted tree. A NotFound error is deliberately tolerated (nothing to migrate); a wrong-type existing path is not.

Source

Thrown at crates/astrid-kernel/src/principal_home_migration/unbound.rs:38

const QUARANTINE_DIR: &str = "unbound-legacy-homes";

/// Mint identities for leftover valid aliases and quarantine invalid names.
///
/// Call this only on the first layout-1 cut-over, before the barrier snapshots
/// admitted bindings. Existing-v2 leftover sources still fail closed later.
pub(crate) async fn admit_unbound_legacy_principal_homes(
    home: &AstridHome,
    directory: &PrincipalDirectory,
    identity: &dyn IdentityStore,
) -> io::Result<()> {
    let source_root = home.home_dir();
    let metadata = match fs::symlink_metadata(&source_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 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?);

View on GitHub (pinned to affd8760f4)

Solutions

  1. Replace the symlink/non-directory at the legacy home path with a real directory containing the legacy principal homes.
  2. Point migration's source_root configuration at the actual legacy directory rather than a link to it.
  3. Check mount points and restores: ensure the expected directory structure exists at the configured path.
  4. Remove any stale non-directory placeholder and re-run migration.

Example fix

// before
mv ~/.legacy-homes ~/.legacy-homes-real
ln -s ~/.legacy-homes-real ~/.legacy-homes  // symlink is rejected
// after
rm ~/.legacy-homes
mv ~/.legacy-homes-real ~/.legacy-homes      // real directory
Defensive patterns

Strategy: validation

Validate before calling

fn legacy_root_ok(path: &Path) -> bool {
    match fs::symlink_metadata(path) {
        Ok(m) => !m.file_type().is_symlink() && m.is_dir(),
        Err(e) => e.kind() == io::ErrorKind::NotFound, // tolerated: nothing to migrate
    }
}

Try / catch

match admit_unbound_legacy_principal_homes(&source_root, ...) {
    Ok(()) => (),
    Err(e) if e.to_string().contains("not a regular directory") => {
        // replace symlink/file with a real directory, then retry
        return Err(e.into_config_error("fix legacy home root path"));
    }
    Err(e) => return Err(e),
}

Prevention

When it happens

Trigger: admit_unbound_legacy_principal_homes calls fs::symlink_metadata on the legacy home root and finds is_symlink() true or is_dir() false (regular file, FIFO, device node, etc.).

Common situations: The legacy home path was replaced by a symlink to relocate storage; the path points at a regular file due to a botched copy/restore; a container or mount maps the home path to a non-directory.

Related errors


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