astrid-runtime/astrid · error
legacy source contains redirect or special entry: {}
Error message
legacy source contains redirect or special entry: {} What it means
A child of the legacy tree being retired is a symlink or a special file (FIFO, socket, device). retire_tree only deletes plain files and directories; anything that could redirect the walk or have side effects on open is rejected with InvalidData, naming the offending child.
Source
Thrown at crates/astrid-kernel/src/legacy_migration_barrier/host_fs.rs:286
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 {
return Err(io::Error::new(
io::ErrorKind::InvalidData,
format!(
"legacy source crosses a mount or device boundary: {}",
child.display()
),
));
}
if child_meta.is_dir() {
let child_snapshot = snapshot_path(&child)?;View on GitHub (pinned to affd8760f4)
Solutions
- Remove or replace the reported symlink/special entry: `rm` the symlink/FIFO/socket, then `cp -a` the real file if one is needed.
- Stop any legacy process that may own the socket/FIFO before removing it.
- Verify the tree contains only regular files and directories (`find <path> ! -type f ! -type d`) before rerunning migration.
- Rerun the migration; the check is per-entry, so clean all offenders the message reports.
Example fix
# before ln -s /mnt/big/archive.dat ~/.astrid/legacy/db/archive.dat // after rm ~/.astrid/legacy/db/archive.dat cp -a /mnt/big/archive.dat ~/.astrid/legacy/db/archive.dat
Defensive patterns
Strategy: validation
Validate before calling
use std::fs;
fn tree_only_regular_entries(path: &std::path::Path) -> std::io::Result<Vec<std::path::PathBuf>> {
let mut bad = Vec::new();
for entry in walkdir_like(path) {
let m = fs::symlink_metadata(&entry)?;
if m.file_type().is_symlink() || (!m.is_file() && !m.is_dir()) {
bad.push(entry);
}
}
Ok(bad) // must be empty before migration
} Type guard
fn is_plain_entry(m: &std::fs::Metadata) -> bool {
!m.file_type().is_symlink() && (m.is_file() || m.is_dir())
} Try / catch
match retire_tree(path, &expected, &protected) {
Err(e) if e.to_string().contains("redirect or special entry") => {
let child = extract_path(&e.to_string());
eprintln!("remove or replace {child} with a regular file");
}
other => other?,
} Prevention
- Pre-scan with `find <legacy-path> ! -type f ! -type d` and clean all hits before migrating
- Stop legacy daemons so they stop recreating sockets/FIFOs in the tree
- Never symlink large files into the legacy tree to save space during migration
When it happens
Trigger: retire_tree (and retire_leaf's re-check) calls symlink_metadata on each child; `child_meta.file_type().is_symlink() || (!is_file() && !is_dir())` matches — a symlink swapped in for a regular file, a leftover socket/FIFO from a legacy daemon, or a device node. A second check in retire_leaf catches entries swapped between the directory scan and unlink.
Common situations: Legacy daemons leaving Unix sockets inside their state dirs; users symlinking large files into the legacy tree to save space; backup/sync tools creating hardlink-like shortcuts; compromised or tampered trees.
Understand the failure class
Background: Path traversal blocked: "path escapes the workspace" and "outside site root" errors when a path will not stay inside its allowed directory — this error's family across 26 libraries.
Related errors
- legacy retirement root is not a directory: {}
- directory symlink {} not allowed in capsule source tree (ref
- legacy capsule directory is not a regular directory: {}
- legacy capsule entry is not a regular directory: {}
- capsule path escaped source root
AI-assisted analysis of astrid-runtime/astrid@affd8760f4 (2026-09-09).
Data as JSON: /api/errors/b1cd6ae62ad103c2.
Report an issue: GitHub.