astrid-runtime/astrid · error

legacy capsule directory is not a regular directory: {}

Error message

legacy capsule directory is not a regular directory: {}

What it means

migrate_native_capsules_with_report inspects the legacy native capsule directory (~/.astrid/native or similar) before migrating. If that root path exists but is a symlink or not a regular directory, migration aborts instead of migrating through an unexpected filesystem object.

Source

Thrown at crates/astrid-capsule-install/src/storage/migration.rs:109

    store: &Arc<RuntimePrincipalStore>,
    home: &astrid_core::dirs::AstridHome,
    principal: &PrincipalId,
    workspace_targets: &[std::path::PathBuf],
) -> anyhow::Result<LegacyCapsuleMigrationReport> {
    let uid = store
        .principal_directory()
        .uid_for(principal)
        .with_context(|| format!("resolve durable uid for principal {principal}"))?;
    let native = home.principal_home(principal).capsules_dir();
    let metadata = match fs::symlink_metadata(&native) {
        Ok(metadata) => metadata,
        Err(error) if error.kind() == io::ErrorKind::NotFound => {
            return Ok(LegacyCapsuleMigrationReport::default());
        },
        Err(error) => return Err(error).with_context(|| format!("inspect {}", native.display())),
    };
    if metadata.file_type().is_symlink() || !metadata.is_dir() {
        bail!(
            "legacy capsule directory is not a regular directory: {}",
            native.display()
        );
    }
    astrid_core::platform_fs::verify_no_redirects(&native)
        .with_context(|| format!("verify legacy capsule root {}", native.display()))?;
    let mut children = read_dir_sorted(&native)?;
    let mut report = LegacyCapsuleMigrationReport::default();
    let registry = store.capsules();
    let owner = StateOwner::Principal(uid);
    for (target, target_metadata) in children.drain(..) {
        if target_metadata.file_type().is_symlink() || !target_metadata.is_dir() {
            bail!(
                "legacy capsule entry is not a regular directory: {}",
                target.display()
            );
        }
        let id = target

View on GitHub (pinned to affd8760f4)

Solutions

  1. Remove the symlink/file at the native path and restore a real directory
  2. Move the real data into the expected directory location directly
  3. Point the home/config setting at the actual directory instead of symlinking
  4. Restore the directory from backup with directory type preserved

Example fix

# before
ln -s ~/Dropbox/astrid-native ~/.astrid/native
# after
mv ~/Dropbox/astrid-native/* ~/.astrid/native/  # real directory, no symlink
Defensive patterns

Strategy: validation

Validate before calling

use std::fs;
fn native_root_is_plain_dir(p: &Path) -> Result<(), String> {
    let md = fs::symlink_metadata(p).map_err(|e| e.to_string())?;
    if md.file_type().is_symlink() { return Err("symlink".into()); }
    if !md.is_dir() { return Err("not a directory".into()); }
    Ok(())
}

Type guard

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

Try / catch

match migrate_native_capsules(home, store) {
    Err(e) if e.to_string().contains("not a regular directory") => {
        eprintln!("fix the native capsule path (remove symlink/file) and retry");
    }
    other => other?,
}

Prevention

When it happens

Trigger: The native capsule directory is a symlink (e.g. pointed at Dropbox/Network drive or another location), a plain file was created at that path, or a mount/hardlink oddity replaced the directory.

Common situations: User symlinked their config/data directory into a sync folder; backup tool restored the path as a file; automount points presenting a non-directory node.

Related errors


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