astrid-runtime/astrid · error

legacy audit tree is redirected or not a directory

Error message

legacy audit tree is redirected or not a directory: {}

What it means

validate_audit_tree checks each node of the legacy audit tree (no-follow, via symlink_metadata) and rejects any node that is a symlink or not a directory with InvalidData. The migration barrier requires the audit tree to consist solely of plain directories and regular files; redirected nodes could point outside the tree and cause data outside the legacy layout to be renamed or deleted.

Solutions

  1. Replace every symlink inside (or at the root of) the audit tree with a real directory containing the data.
  2. Remove or rename non-directory entries that occupy paths expected to be directories.
  3. Re-run the migration after the tree contains only plain directories and regular files.

Example fix

// before
ln -s /var/lib/audit ~/.astrid/principal/audit
// after
rm ~/.astrid/principal/audit
mv /var/lib/audit ~/.astrid/principal/audit
Defensive patterns

Strategy: validation

Validate before calling

fn audit_tree_is_plain(root: &Path) -> std::io::Result<bool> {
    for entry in std::fs::read_dir(root)? {
        let m = std::fs::symlink_metadata(entry?.path())?;
        if m.file_type().is_symlink() { return Ok(false); }
    }
    Ok(true)
}

Type guard

fn is_plain_tree_node(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.to_string().contains("redirected or not a directory") {
        // dereference/replace symlinks, then retry
    }
}

Prevention

When it happens

Trigger: Preflighting, retiring, or deleting a legacy audit tree while any node on the path (root or reached during recursion) is a symlink or a non-directory (file passed at directory level, fifo, etc.). Called from preflight_legacy_audit_sources, retire_legacy_audit_dir, validate_audit_tree (recursive), and delete_audit_tree.

Common situations: Dotfile managers symlinking parts of ~/.astrid; an 'audit' path that is actually a file; symlinked subdirectories inside the audit tree pointing to external storage.

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

Appendix: source

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

        },
        Err(error) if error.kind() == io::ErrorKind::NotFound => {
            if fs::symlink_metadata(&retired).is_ok() {
                let root_device = device_id(&fs::symlink_metadata(&retired)?);
                validate_audit_tree(&retired, root_device)?;
                delete_audit_tree(&retired, root_device)?;
                sync_directory(&home.migrations_dir())?;
            }
        },
        Err(error) => return Err(error),
    }
    Ok(())
}

#[cfg(not(unix))]
fn validate_audit_tree(path: &Path, root_device: u64) -> io::Result<()> {
    let metadata = fs::symlink_metadata(path)?;
    if metadata.file_type().is_symlink() || !metadata.is_dir() {
        return Err(io::Error::new(
            io::ErrorKind::InvalidData,
            format!(
                "legacy audit tree is redirected or not a directory: {}",
                path.display()
            ),
        ));
    }
    if device_id(&metadata) != root_device || active_mountpoint(path)? {
        return Err(io::Error::new(
            io::ErrorKind::InvalidData,
            format!(
                "legacy audit tree crosses a filesystem or mount boundary: {}",
                path.display()
            ),
        ));
    }
    astrid_core::platform_fs::verify_no_redirects(path)?;
    for entry in fs::read_dir(path).map_err(io::Error::other)? {

View on GitHub (pinned to affd8760f4)