astrid-runtime/astrid · error

legacy state source crosses a filesystem boundary: {}

Error message

legacy state source crosses a filesystem boundary: {}

What it means

ensure_legacy_tree_boundary compares the st_dev of each directory in the legacy tree against the root device. If any entry lies on a different filesystem (i.e. the tree crosses a mount boundary), it throws InvalidData so deletion never recurses into a separate filesystem and wipes unrelated data.

Source

Thrown at crates/astrid-core/src/dirs_layout_retirement.rs:236

    // later operator repair or an idempotent restart.
    let root_device = legacy_tree_device(&metadata);
    validate_legacy_tree(path, root_device)?;
    delete_legacy_tree(path, root_device)?;
    let parent = path
        .parent()
        .ok_or_else(|| io::Error::other("legacy state source has no parent"))?;
    sync_directory(parent)
}

fn ensure_legacy_tree_boundary(
    path: &Path,
    root_device: u64,
    metadata: &std::fs::Metadata,
) -> io::Result<()> {
    #[cfg(unix)]
    {
        if legacy_tree_device(metadata) != root_device {
            return Err(io::Error::new(
                io::ErrorKind::InvalidData,
                format!(
                    "legacy state source crosses a filesystem boundary: {}",
                    path.display()
                ),
            ));
        }
    }
    #[cfg(not(unix))]
    let _ = (root_device, metadata);
    if is_active_mountpoint(path)? {
        return Err(io::Error::new(
            io::ErrorKind::InvalidData,
            format!("legacy state source is an active mount: {}", path.display()),
        ));
    }
    Ok(())
}

View on GitHub (pinned to affd8760f4)

Solutions

  1. Unmount the nested filesystem (umount <reported path>) so the whole tree lives on one device, then retry.
  2. Relocate the mounted content into the same filesystem and re-check.
  3. Use a bind mount at the tree root itself is NOT allowed either — the entire tree must be on one device, so flatten the layout.

Example fix

// before
mount | grep legacy  -> tmpfs on /data/legacy/tmp
// after
umount /data/legacy/tmp
Defensive patterns

Strategy: validation

Validate before calling

#[cfg(unix)]
fn same_device(p: &Path, root_dev: u64) -> io::Result<bool> {
    use std::os::unix::fs::MetadataExt;
    Ok(std::fs::metadata(p)?.dev() == root_dev)
}

Type guard

fn crosses_boundary(md: &std::fs::Metadata, root_dev: u64) -> bool {
    use std::os::unix::fs::MetadataExt;
    md.dev() != root_dev
}

Try / catch

match retire_legacy_source_tree(&path, dev) {
    Err(e) if e.to_string().contains("filesystem boundary") => eprintln!("umount nested fs first"),
    other => other?,
}

Prevention

When it happens

Trigger: Any subdirectory inside the legacy tree is a mountpoint or was bind-mounted from another device when validate_legacy_tree or delete_legacy_tree checks boundaries.

Common situations: An admin mounted extra storage at a nested state subdirectory (e.g. tmpfs at legacy/tmp); Docker/Kubernetes volume mounts placed inside the data dir; disk re-partitioning moved part of the tree.

Related errors


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