astrid-runtime/astrid · error
legacy source changed type
Error message
legacy source changed type: {} What it means
This error is thrown by `read_regular_file` when a legacy source path that was expected to be a regular file no longer is. After opening the file with O_NOFOLLOW (so symlinks fail at open) and O_NONBLOCK, the code calls `file.metadata()` and rejects anything whose type is not a plain file. The library throws this to guarantee the legacy source being migrated cannot silently change identity between path validation and read.
Solutions
- Inspect the path reported in the message with `ls -la` and `stat` to see what it actually is.
- Remove or relocate the special entry so the legacy source contains only regular files.
- Re-run the migration; ensure no external process rewrites the legacy directory during migration.
Example fix
// before: path is a FIFO left by another tool $ mkfifo ~/.legacy-app/state // after: remove the special entry so migration sees a regular file $ rm ~/.legacy-app/state
Defensive patterns
Strategy: validation
Validate before calling
let md = std::fs::symlink_metadata(path)?;
if !md.is_file() {
return Err(anyhow!("skip migration: {} is not a regular file", path.display()));
} Type guard
fn is_regular_file(p: &std::path::Path) -> bool {
std::fs::symlink_metadata(p).map(|m| m.is_file()).unwrap_or(false)
} Try / catch
match astrid_kernel::read_regular_file(path) {
Err(e) if e.to_string().contains("legacy source changed type") => {
log::warn!("skipping special entry {}", path.display());
}
Err(e) => return Err(e.into()),
Ok(data) => { /* proceed */ }
} Prevention
- Scan legacy directories for non-regular entries with `find <dir> ! -type f ! -type d` before migrating.
- Keep daemons and sync tools from creating sockets/FIFOs inside legacy paths during migration.
- Open sources with O_NOFOLLOW semantics yourself to fail fast on symlink swaps.
When it happens
Trigger: Calling read_regular_file (via snapshot_path_with_access or snapshot_dir) on a path that is a FIFO, device, socket, or changed type after validation was performed.
Common situations: A special file (e.g. /dev entry or FIFO) is present in the legacy home directory; a file was replaced by a symlink or device node mid-migration; a security tool or another process mutated the legacy layout concurrently.
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
- legacy principal home root is not a regular directory
- legacy principal profile is not a regular file
- layout migration destination changed type
- layout migration destination changed while inventoried
- layout migration destination is redirected or not a regular…
AI-assisted analysis of astrid-runtime/astrid@affd8760f4 (2026-09-09).
Data as JSON: /api/errors/eabe832df8dc30d3.
Report an issue: GitHub.
Appendix: source
Thrown at crates/astrid-kernel/src/legacy_migration_barrier/host_fs.rs:817
validate_private_entry(path, metadata)
}
}
fn read_regular_file(
path: &Path,
hasher: &mut blake3::Hasher,
identity: &mut SourceInventory,
) -> io::Result<()> {
let mut options = OpenOptions::new();
options.read(true);
#[cfg(unix)]
{
use std::os::unix::fs::OpenOptionsExt as _;
options.custom_flags(nix::libc::O_NOFOLLOW | nix::libc::O_CLOEXEC | nix::libc::O_NONBLOCK);
}
let mut file = options.open(path)?;
if !file.metadata()?.is_file() {
return Err(io::Error::new(
io::ErrorKind::InvalidData,
format!("legacy source changed type: {}", path.display()),
));
}
let mut buffer = vec![0_u8; 64 * 1024].into_boxed_slice();
loop {
let read = file.read(&mut buffer)?;
if read == 0 {
break;
}
identity.bytes = identity
.bytes
.checked_add(read as u64)
.ok_or_else(|| io::Error::other("legacy source byte limit exceeded"))?;
if identity.bytes.get() > MAX_BYTES {
return Err(io::Error::other("legacy source byte limit exceeded"));
}
hasher.update(&buffer[..read]);View on GitHub (pinned to affd8760f4)