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

layout migration source contains a redirect: {}

Error message

layout migration source contains a redirect: {}

What it means

During a layout-v2 migration, the inventory walk (inventory_directory, driven by inventory_tree) found a symlink inside the legacy state directory it is fingerprinting. The migration refuses to hash trees containing redirects because a symlink could change what data the inventory digest covers and could escape the state directory boundary. The operation aborts with io::ErrorKind::InvalidData naming the offending path.

Source

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

    })
}

fn inventory_directory(
    root: &Path,
    directory: &Path,
    hasher: &mut blake3::Hasher,
    entries: &mut u64,
    bytes: &mut u64,
) -> io::Result<()> {
    let mut children = std::fs::read_dir(directory)?.collect::<Result<Vec<_>, _>>()?;
    children.sort_by_key(std::fs::DirEntry::file_name);
    for child in children {
        let child_path = child.path();
        let relative = child_path.strip_prefix(root).map_err(io::Error::other)?;
        let relative_bytes = relative.as_os_str().as_encoded_bytes();
        let metadata = std::fs::symlink_metadata(&child_path)?;
        if metadata.file_type().is_symlink() {
            return Err(io::Error::new(
                io::ErrorKind::InvalidData,
                format!(
                    "layout migration source contains a redirect: {}",
                    child_path.display()
                ),
            ));
        }
        super::retirement::validate_legacy_surrealkv_entry(
            relative,
            metadata.is_dir(),
            metadata.is_file(),
        )?;
        *entries = entries
            .checked_add(1)
            .ok_or_else(|| io::Error::other("layout inventory entry count overflow"))?;
        hash_inventory_field(hasher, b"path", relative_bytes);
        if metadata.is_dir() {
            hasher.update(b"directory");

View on GitHub (pinned to affd8760f4)

Solutions

  1. Find the symlink named in the message and remove it, copying real data in its place (rm the link, cp -L the target contents).
  2. Re-point your dotfile/symlink manager at the state directory as a whole rather than entries inside it.
  3. If the whole state directory should live elsewhere, migrate the entire directory (no symlinks inside) and ensure the configured path itself points to the real location.
  4. Re-run the migration once the tree contains only real files and directories.

Example fix

// before: state dir contains a symlink
ln -s /mnt/fast/surrealkv ~/.local/state/astrid/surrealkv
// after: real directory, link placed one level up
mv ~/real/astrid-state ~/.local/state/astrid  # move real data
rm ~/.local/state/astrid/surrealkv            # remove the symlink first
Defensive patterns

Strategy: validation

Validate before calling

use std::path::Path;
fn assert_no_symlinks(dir: &Path) -> std::io::Result<()> {
    for entry in std::fs::read_dir(dir)? {
        let entry = entry?;
        let meta = std::fs::symlink_metadata(entry.path())?;
        if meta.file_type().is_symlink() {
            return Err(std::io::Error::new(
                std::io::ErrorKind::InvalidData,
                format!("symlink in state tree: {}", entry.path().display()),
            ));
        }
        if meta.is_dir() {
            assert_no_symlinks(&entry.path())?;
        }
    }
    Ok(())
}

Type guard

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

Prevention

When it happens

Trigger: Running a layout v2 migration (inventory_tree/inventory_directory) while any child entry of the source directory — at any depth — is a symlink (symlink_metadata reports is_symlink).

Common situations: Users symlink state directories into dotfiles managers (e.g. stow, GNU stow/dotbot) or to another disk; a backup/restore tool replaced a subdirectory with a symlink; someone hand-linked a file from elsewhere into the state tree.

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