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
- Identify the reported child: if a component recreated it, stop that component and confirm it has migrated to the new layout before retrying.
- 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.
- Verify the protected-path list passed to retirement is correct — overly broad protection entries cause false conflicts.
- 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
- Ensure all components have migrated (and their services stopped) before post-barrier retirement
- Keep the protected-path list precise; stale or overly broad entries cause false conflicts
- Re-run retirement after cleanup — the check is per-entry and idempotent for already-removed trees
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
- legacy source changed before retirement: {}
- legacy capsule entry has a non-UTF-8 name
- workspace capsule manifest changed while it was being read:
- read capsule projection entry: {error}
- inspect capsule projection {}: {error}
AI-assisted analysis of astrid-runtime/astrid@affd8760f4 (2026-09-09).
Data as JSON: /api/errors/f7b02b4ed56db47d.
Report an issue: GitHub.