astrid-runtime/astrid · error

legacy component source reappeared during ordinary retiremen

Error message

legacy component source reappeared during ordinary retirement: {}

What it means

While performing ordinary (bottom-up) retirement, retire_tree found a child of the legacy tree that is listed in the `protected` set — a component-owned path that must never be swept (e.g. a component re-created its state directory after the barrier). Deleting it would destroy live component data, so the library aborts with InvalidData.

Source

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

        return Err(io::Error::new(
            io::ErrorKind::InvalidData,
            format!(
                "legacy retirement root is not a directory: {}",
                path.display()
            ),
        ));
    }
    if active_mountpoint(path)? {
        return Err(io::Error::new(
            io::ErrorKind::InvalidData,
            format!("legacy source is an active mount: {}", path.display()),
        ));
    }
    let device = device_id(&metadata);
    for entry in fs::read_dir(path).map_err(io::Error::other)? {
        let child = entry.map_err(io::Error::other)?.path();
        if protected.iter().any(|candidate| candidate == &child) {
            return Err(io::Error::new(
                io::ErrorKind::InvalidData,
                format!(
                    "legacy component source reappeared during ordinary retirement: {}",
                    child.display()
                ),
            ));
        }
        let child_meta = fs::symlink_metadata(&child).map_err(io::Error::other)?;
        if child_meta.file_type().is_symlink() || (!child_meta.is_file() && !child_meta.is_dir()) {
            return Err(io::Error::new(
                io::ErrorKind::InvalidData,
                format!(
                    "legacy source contains redirect or special entry: {}",
                    child.display()
                ),
            ));
        }
        if active_mountpoint(&child)? || device_id(&child_meta) != device {

View on GitHub (pinned to affd8760f4)

Solutions

  1. Identify the reported child: if a component recreated it, stop that component and confirm it has migrated to the new layout before retrying.
  2. If the child is stale and genuinely legacy, remove it from the protected set (or delete it via the component's supported path) and rerun retirement.
  3. Verify the protected-path list passed to retirement is correct — overly broad protection entries cause false conflicts.
  4. Never force-delete protected paths by hand; route deletion through the owning component or migration tooling.

Example fix

# before
systemctl start legacy-component   # recreates its state dir during retirement
// after
systemctl stop legacy-component
astrid migrate   # protected path no longer reappears; retirement completes
Defensive patterns

Strategy: try-catch

Validate before calling

// Confirm no protected child exists in the legacy tree before retiring
fn protected_children(path: &std::path::Path, protected: &[std::path::PathBuf]) -> std::io::Result<Vec<std::path::PathBuf>> {
    Ok(std::fs::read_dir(path)?
        .filter_map(|e| e.ok().map(|e| e.path()))
        .filter(|c| protected.iter().any(|p| p == c))
        .collect())
}

Try / catch

match retire_tree(path, &expected, &protected) {
    Err(e) if e.to_string().contains("reappeared during ordinary retirement") => {
        let child = extract_path(&e.to_string());
        eprintln!("stop the owning component and handle {child} via its supported path");
    }
    other => other?,
}

Prevention

When it happens

Trigger: retire_tree iterates read_dir children and checks `protected.iter().any(|candidate| candidate == &child)`; any child path present in the protected list triggers this immediately. Happens when a component recreates or never released a directory that the ledger expected to be gone, or when the protected list was built against paths that still exist at retirement time.

Common situations: A legacy component service restarted mid-migration and recreated its state directory; an incomplete earlier migration left a component-owned directory behind; misconfigured protected paths that overlap ordinary content.

Understand the failure class

Background: "already exists" / EEXIST / FileAlreadyExistsException: what the 'file already exists' error means and how to fix it — this error's family across 37 libraries.

Related errors


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