astrid-runtime/astrid · error
legacy source is not a regular file: {}
Error message
legacy source is not a regular file: {} What it means
validate_source_path checks a legacy migration source path before it is used. It takes symlink_metadata (which does not follow symlinks) and requires the entry to be a plain regular file; a symlink or any non-file (directory, socket, fifo, device node) fails this check with InvalidData. The library does this so migration digests bind to real file bytes, not to redirectable or non-regular entries.
Source
Thrown at crates/astrid-kernel/src/legacy_migration_barrier/host_fs.rs:662
format!("legacy source contains a special entry: {}", path.display()),
));
}
SourceIdentity::present(
SourceDigest::from_blake3(hasher.finalize()),
identity.entries,
identity.bytes,
)
.map_err(|error| io::Error::new(io::ErrorKind::InvalidData, error))
}
/// Validate one regular-file source using the same no-follow, private,
/// same-device, and mount checks as recursive snapshots. Distro lock files
/// keep their component-owned digest format, so they use this structural
/// check instead of the generic tree hash.
pub(super) fn validate_source_path(path: &Path) -> io::Result<()> {
let metadata = fs::symlink_metadata(path)?;
if metadata.file_type().is_symlink() || !metadata.is_file() {
return Err(io::Error::new(
io::ErrorKind::InvalidData,
format!("legacy source is not a regular file: {}", path.display()),
));
}
astrid_core::platform_fs::verify_no_redirects(path)?;
validate_private_entry(path, &metadata)?;
if active_mountpoint(path)? {
return Err(io::Error::new(
io::ErrorKind::InvalidData,
format!("legacy source is an active mount: {}", path.display()),
));
}
if let Some(parent) = path.parent()
&& let Ok(parent_metadata) = fs::symlink_metadata(parent)
&& device_id(&parent_metadata) != device_id(&metadata)
{
return Err(io::Error::new(
io::ErrorKind::InvalidData,View on GitHub (pinned to affd8760f4)
Solutions
- Resolve the path to its final target and pass the real regular file (e.g. fs::canonicalize, then re-check it is a file).
- ls -la the path and confirm it is a regular file, not a symlink or directory.
- If the target is a directory, use the directory snapshot path (snapshot_owner_controlled_path / snapshot_path_with_access) instead of the file validator.
- Recreate or restore the regular file at that path if a symlink or special file was placed there unintentionally.
Example fix
// before
let src = Path::new("/etc/astrid.lock"); // actually a symlink to /usr/share/astrid/astrid.lock
validate_source_path(src)?;
// after
let src = std::fs::canonicalize("/etc/astrid.lock")?;
let md = std::fs::metadata(&src)?;
assert!(md.is_file());
validate_source_path(&src)?; Defensive patterns
Strategy: validation
Validate before calling
fn is_regular_nonsymlink(path: &std::path::Path) -> std::io::Result<bool> {
let md = std::fs::symlink_metadata(path)?;
Ok(!md.file_type().is_symlink() && md.is_file())
}
if !is_regular_nonsymlink(&src)? {
return Err(format!("{} must be a regular file, not a symlink/dir", src.display()));
} Type guard
fn is_plain_file(path: &std::path::Path) -> bool {
std::fs::symlink_metadata(path)
.map(|md| !md.file_type().is_symlink() && md.is_file())
.unwrap_or(false)
} Try / catch
match validate_source_path(&src) {
Err(e) if e.kind() == std::io::ErrorKind::InvalidData => {
let real = std::fs::canonicalize(&src).unwrap_or(src.clone());
validate_source_path(&real).map_err(|e2| e2)?; // retry against resolved target
}
other => other,
} Prevention
- Always pass canonicalized paths (fs::canonicalize) to the validator.
- Check file type with symlink_metadata before calling library APIs.
- Avoid passing directories to file-oriented validators; use the directory snapshot API.
- Audit config paths for distro-installed symlinks.
When it happens
Trigger: Calling validate_source_path (or the distro lock-file validation flow that uses it) with a path that is a symlink, a directory, or another special file type.
Common situations: Passing a convenience symlink like ~/.config/foo instead of the real file; pointing at a directory when a lock file path was expected; a special file (fifo/socket) created at the expected path; a distro that replaced a regular lock file with a symlink.
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
- directory symlink {} not allowed in capsule source tree (ref
- legacy env/secret path is not a regular directory: {}
- capsule path escaped source root
- capsule projection path is not UTF-8: {}
- inspect projected file {}: {error}
AI-assisted analysis of astrid-runtime/astrid@affd8760f4 (2026-09-09).
Data as JSON: /api/errors/e9f86ba44b06695a.
Report an issue: GitHub.