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

layout migration destination is redirected or not a regular

Error message

layout migration destination is redirected or not a regular file: {}

What it means

verify_receipt_destination_authority validates that the migration receipt's recorded destination is an authoritative, unredirected regular file. It reads symlink_metadata on the destination's physical path and rejects it if the entry is a symlink or not a plain file. This guards against a receipt pointing at a path that an attacker or misconfiguration has redirected (e.g. symlink swap) or that was replaced by a directory or other non-file entry.

Source

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

    if actual != expected {
        return Err(io::Error::new(
            io::ErrorKind::InvalidData,
            format!(
                "layout migration record is not canonical: {}",
                path.display()
            ),
        ));
    }
    Ok(parsed)
}

pub(super) fn verify_receipt_destination_authority(
    destination: &LayoutTreeIdentityV1,
) -> io::Result<()> {
    let path = physical_path(destination)?;
    let metadata = std::fs::symlink_metadata(&path)?;
    if metadata.file_type().is_symlink() || !metadata.is_file() {
        return Err(io::Error::new(
            io::ErrorKind::InvalidData,
            format!(
                "layout migration destination is redirected or not a regular file: {}",
                path.display()
            ),
        ));
    }
    crate::platform_fs::verify_no_redirects(&path)
}

pub(super) fn verify_receipt_destination_is_live_path(
    destination: &LayoutTreeIdentityV1,
    live_path: &Path,
) -> io::Result<()> {
    let parent = live_path.parent().ok_or_else(|| {
        io::Error::new(
            io::ErrorKind::InvalidInput,
            "live Astrid volume has no parent",

View on GitHub (pinned to affd8760f4)

Solutions

  1. Remove the symlink at the destination path and replace it with the real regular file the receipt points to.
  2. Re-create or re-issue the layout migration receipt so its LayoutTreeIdentityV1 matches the current real on-disk path.
  3. Inspect the path with `ls -la` / `stat` to see what replaced the regular file and restore it (e.g. from the migration source).

Example fix

// before: destination replaced by symlink
ln -s /mnt/other/volume /path/to/astrid-volume
// after: real regular file at the destination
rm /path/to/astrid-volume
cp /mnt/other/volume /path/to/astrid-volume   # then re-run retirement
Defensive patterns

Strategy: validation

Validate before calling

let md = std::fs::symlink_metadata(&dest_path)?;
if md.file_type().is_symlink() || !md.is_file() {
    return Err("receipt destination is not an unredirected regular file");
}

Try / catch

match retire_verified_legacy_source(...) {
    Err(e) if e.to_string().contains("redirected or not a regular file") => restore_real_destination_file(),
    Err(e) => return Err(e),
    Ok(()) => {},
}

Prevention

When it happens

Trigger: Called from verify_receipt_destination_is_live_path (itself called by retire_verified_legacy_source) whenever the path returned by physical_path(destination) is a symlink, directory, FIFO, socket, or any non-regular file when checked via std::fs::symlink_metadata.

Common situations: The Astrid volume path was replaced by a symlink (e.g. to a mount point or tmpfs alias) after the receipt was written; the destination file was deleted and a directory created with the same name; a backup/restore tool replaced the volume file 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/a43cf4e078b7a937. Report an issue: GitHub.