astrid-runtime/astrid · error
legacy source contains redirect
Error message
legacy source contains redirect: {} What it means
During recursive directory snapshotting, snapshot_dir examines every child with symlink_metadata (no-follow). Any child that is a symlink is rejected as a "redirect" with InvalidData, because a symlink could point anywhere and would make the snapshot digest unrepresentative and the migration unsafe.
Solutions
- Replace the symlink with a real copy of its target (cp -L --remove-destination), then re-run the snapshot.
- Remove the symlink if it is not needed.
- Exclude the symlinked entry from the source directory and snapshot a symlink-free tree.
- If the symlink is intentional, copy the whole tree to a staging directory dereferencing symlinks and snapshot that.
Example fix
// before legacy_dir/ current -> v2/ # symlink: snapshot_dir rejects it // after cp -rL legacy_dir legacy_dir_flat # dereference symlinks into real files rm legacy_dir/current && mv legacy_dir_flat/v2 legacy_dir/current # or just snapshot legacy_dir_flat
Defensive patterns
Strategy: validation
Validate before calling
fn tree_has_symlinks(root: &std::path::Path) -> std::io::Result<bool> {
for entry in walkdir::WalkDir::new(root).follow_links(false) {
let entry = entry?;
if std::fs::symlink_metadata(entry.path())?.file_type().is_symlink() {
return Ok(true);
}
}
Ok(false)
}
assert!(!tree_has_symlinks(&src_dir)?, "tree contains symlinks"); Type guard
fn symlink_free_tree(root: &std::path::Path) -> bool {
!tree_has_symlinks(root).unwrap_or(true)
} Try / catch
match snapshot_owner_controlled_path(&dir) {
Err(e) if e.to_string().contains("contains redirect") => {
let flat = copy_dereferencing(&dir, "/tmp/astrid-flat")?; // cp -rL equivalent
snapshot_owner_controlled_path(&flat)
}
other => other,
} Prevention
- Scan the tree with `find <dir> -type l` before snapshotting.
- Replace intentional symlinks with real copies in legacy directories.
- Avoid snapshotting directories that hold version-alias symlinks.
- Keep symlinks outside the migration source tree.
When it happens
Trigger: Calling snapshot_path_with_access / snapshot_owner_controlled_path on a directory whose tree contains a symlink at any depth (snapshot_dir iterates children recursively).
Common situations: Legacy config directories containing convenience symlinks (e.g. current -> v2, rc -> rc.d); shared library or plugin dirs with symlinked versions; user-created aliases inside a state directory.
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
- capsule path escaped source root
- directory symlink not allowed in capsule source tree…
- inspect projected file
- legacy audit source is not a regular directory
- legacy audit tree is redirected or not a directory
AI-assisted analysis of astrid-runtime/astrid@affd8760f4 (2026-09-09).
Data as JSON: /api/errors/f8cd75b7c63bb7da.
Report an issue: GitHub.
Appendix: source
Thrown at crates/astrid-kernel/src/legacy_migration_barrier/host_fs.rs:708
fn snapshot_dir(
root: &Path,
dir: &Path,
device: u64,
access: SourceAccess,
hasher: &mut blake3::Hasher,
identity: &mut SourceInventory,
) -> io::Result<()> {
let mut children = fs::read_dir(dir)
.map_err(io::Error::other)?
.collect::<Result<Vec<_>, _>>()
.map_err(io::Error::other)?;
children.sort_by_key(std::fs::DirEntry::file_name);
for entry in children {
let path = entry.path();
let relative = path.strip_prefix(root).map_err(io::Error::other)?;
let metadata = fs::symlink_metadata(&path).map_err(io::Error::other)?;
if metadata.file_type().is_symlink() {
return Err(io::Error::new(
io::ErrorKind::InvalidData,
format!("legacy source contains redirect: {}", path.display()),
));
}
validate_source_entry(&path, &metadata, access)?;
if device_id(&metadata) != device {
return Err(io::Error::new(
io::ErrorKind::InvalidData,
format!(
"legacy source crosses a device boundary: {}",
path.display()
),
));
}
if active_mountpoint(&path)? {
return Err(io::Error::new(
io::ErrorKind::InvalidData,
format!("legacy source is an active mount: {}", path.display()),View on GitHub (pinned to affd8760f4)