astrid-runtime/astrid · error

legacy audit source is not a regular directory: {}

Error message

legacy audit source is not a regular directory: {}

What it means

retire_legacy_audit_dir refuses to rename the legacy audit source unless it is a plain, non-symlink directory. Using symlink_metadata (which does not follow symlinks), it rejects the path with InvalidData if the entry is a symlink or any non-directory file type, because retiring a redirected or non-directory path could destroy or bypass data outside the expected legacy layout.

Source

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

    let retired = home.migrations_dir().join("audit-principal-home.retired");
    let expected = home.principal_home(&PrincipalId::default()).audit_dir();
    if source != expected {
        return Err(io::Error::new(
            io::ErrorKind::InvalidInput,
            "legacy audit retirement source is outside the default principal audit path",
        ));
    }
    astrid_core::platform_fs::verify_no_redirects(source.parent().ok_or_else(|| {
        io::Error::new(
            io::ErrorKind::InvalidInput,
            "legacy audit source has no parent",
        )
    })?)?;
    astrid_core::platform_fs::ensure_private_directory(&home.migrations_dir())?;
    astrid_core::platform_fs::verify_no_redirects(&home.migrations_dir())?;
    match fs::symlink_metadata(source) {
        Ok(metadata) if metadata.file_type().is_symlink() || !metadata.is_dir() => {
            return Err(io::Error::new(
                io::ErrorKind::InvalidData,
                format!(
                    "legacy audit source is not a regular directory: {}",
                    source.display()
                ),
            ));
        },
        Ok(_) => {
            if fs::symlink_metadata(&retired).is_ok() {
                return Err(io::Error::new(
                    io::ErrorKind::AlreadyExists,
                    format!(
                        "legacy audit retirement is ambiguous: {}",
                        retired.display()
                    ),
                ));
            }
            let root_device = device_id(&fs::symlink_metadata(source)?);

View on GitHub (pinned to affd8760f4)

Solutions

  1. Remove the symlink/file at the legacy audit path and replace it with a real directory containing the legacy audit data, then re-run migration.
  2. If the symlink points at the real data, remove the link, move the data back into a plain directory at the expected path, and retry.
  3. Delete an empty or stale non-directory 'audit' entry so migration treats the source as absent and skips retirement.

Example fix

// before: audit is a symlink
rm ~/ .astrid/principal/audit   # symlink
ln -s /mnt/big/audit ~/.astrid/principal/audit
// after: real directory
rm ~/.astrid/principal/audit
mv /mnt/big/audit ~/.astrid/principal/audit
Defensive patterns

Strategy: validation

Validate before calling

match std::fs::symlink_metadata(&source) {
    Ok(m) if m.file_type().is_symlink() || !m.is_dir() => {
        return Err(format!("{} must be a real directory", source.display()));
    },
    Ok(_) | Err(e) if e.kind() == std::io::ErrorKind::NotFound => {},
    Err(e) => return Err(e.into()),
}

Type guard

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

Try / catch

if let Err(e) = migrate_legacy_audit(&home, &source) {
    if e.kind() == std::io::ErrorKind::InvalidData && e.to_string().contains("not a regular directory") {
        // replace symlink/file with a real directory, then retry
    }
}

Prevention

When it happens

Trigger: Calling migrate_legacy_audit when <principal home>/audit is a symlink (e.g. to another location), a regular file, fifo, socket, or other non-directory entry.

Common situations: Users symlinking the audit directory to a bigger disk or dotfiles-managed location; a stale file named 'audit' left where the directory used to be; dotfile managers (stow,chezmoi) replacing the directory with a symlink.

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/ca6d358609206a20. Report an issue: GitHub.