astrid-runtime/astrid · error · io::Error

layout migration source is redirected or not a directory

Error message

layout migration source is redirected or not a directory: {}

What it means

inventory_tree inventories a migration source directory to fingerprint it into a LayoutTreeIdentityV1. If symlink_metadata on the source succeeds but the entry is a symlink or not a directory, the function refuses with this InvalidData error rather than hashing a redirected or wrong-kind target. (A NotFound source is tolerated and hashed as "absent"; other IO errors propagate as-is.)

Solutions

  1. Replace the symlink at the source path with the real directory (or repoint the migration at the real directory).
  2. Verify the source path with `ls -la` and ensure it is an actual directory, not a file.
  3. If the legacy source is legitimately gone, ensure it is absent (NotFound is accepted) rather than replaced by a wrong-kind entry.

Example fix

// before: source replaced by symlink
ln -s /data/new-layout /data/legacy-layout
// after: real directory
rm /data/legacy-layout
mkdir /data/legacy-layout  # or move the real directory back
Defensive patterns

Strategy: validation

Validate before calling

let md = std::fs::symlink_metadata(&source_dir)?;
match md {
    md if md.is_dir() => {},                       // ok
    _ if md.file_type().is_symlink() => return Err("legacy source is a symlink"),
    _ => return Err("legacy source is not a directory"),
}

Try / catch

match retire_verified_legacy_source(...) {
    Err(e) if e.to_string().contains("source is redirected or not a directory") => restore_source_directory(),
    other => other,
}

Prevention

When it happens

Trigger: Called from LayoutRetirementV1, retire_verified_legacy_source, or LayoutMigrationMaterialV1 when the source path is a symlink to a directory, a regular file, or another non-directory entry.

Common situations: Legacy source directory replaced by a symlink to consolidated storage; migration re-run after the source directory was deleted and a file created in its place; users pointing migration at a file instead of the directory.

Related errors


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

Appendix: source

Thrown at crates/astrid-core/src/dirs_layout_records.rs:267

        return Err(io::Error::new(
            io::ErrorKind::InvalidData,
            "layout destination path cannot be represented losslessly on this platform",
        ));
    }
    Ok(path)
}

pub(super) fn inventory_tree(path: &Path) -> io::Result<LayoutTreeIdentityV1> {
    let mut hasher = blake3::Hasher::new_derive_key("astrid layout source inventory v1");
    let mut entries = 0_u64;
    let mut bytes = 0_u64;
    match std::fs::symlink_metadata(path) {
        Err(error) if error.kind() == io::ErrorKind::NotFound => {
            hasher.update(b"absent");
        },
        Err(error) => return Err(error),
        Ok(metadata) if metadata.file_type().is_symlink() || !metadata.is_dir() => {
            return Err(io::Error::new(
                io::ErrorKind::InvalidData,
                format!(
                    "layout migration source is redirected or not a directory: {}",
                    path.display()
                ),
            ));
        },
        Ok(_) => {
            crate::platform_fs::verify_no_redirects(path)?;
            inventory_directory(path, path, &mut hasher, &mut entries, &mut bytes)?;
        },
    }
    Ok(LayoutTreeIdentityV1 {
        path_encoding: "os-str-encoded-bytes-v1".to_owned(),
        physical_path_hex: physical_path_hex(path)?,
        inventory_algorithm: "blake3-derive-key-v1".to_owned(),
        inventory_digest: hasher.finalize().to_hex().to_string(),
        entries,

View on GitHub (pinned to affd8760f4)