astrid-runtime/astrid · error
legacy path {text:?} is not a canonical filesystem path: {er
Error message
legacy path {text:?} is not a canonical filesystem path: {error} What it means
Thrown by logical_relative in crates/astrid-kernel/src/principal_home_migration/paths.rs:107 when a legacy principal-home relative path, after passing the basic canonicality checks (relative, no . / .. / root components, UTF-8, length limit), still fails FilesystemPath::new validation in astrid-storage. It wraps the underlying FilesystemError, so the path cannot be represented as a canonical filesystem path (e.g. empty string, trailing/inner path rules, or other storage-layer invariants violated). The migration fails closed rather than publishing a non-canonical path into the new home layout.
Source
Thrown at crates/astrid-kernel/src/principal_home_migration/paths.rs:107
if path.is_absolute()
|| path
.components()
.any(|component| !matches!(component, Component::Normal(_)))
{
return Err(invalid_source(
path,
"legacy relative path is not canonical",
));
}
let text = path
.to_str()
.ok_or_else(|| invalid_source(path, "legacy relative path is not UTF-8"))?
.replace('\\', "/");
if text.len() > MAX_RELATIVE_PATH_BYTES {
return Err(invalid_source(path, "legacy relative path is too long"));
}
FilesystemPath::new(text.clone()).map_err(|error| {
io::Error::new(
io::ErrorKind::InvalidData,
format!("legacy path {text:?} is not a canonical filesystem path: {error}"),
)
})?;
Ok(text)
}
pub(super) fn destination_name(relative: &str) -> String {
format!("home/{relative}")
}
pub(super) fn is_dedicated_path(path: &Path) -> bool {
let components = path
.components()
.filter_map(|component| match component {
Component::Normal(value) => value.to_str(),
_ => None,
})View on GitHub (pinned to affd8760f4)
Solutions
- Inspect the path printed in the error and remove/rename the offending entry under the legacy home so the relative path is canonical (plain normal components, forward slashes).
- Re-run the migration; if the path should be legal, verify astrid-storage's FilesystemPath::new canonicality rules and adjust the legacy tree to match.
- If the entry is not needed, delete it from the legacy source directory before migration.
- Ensure no symlinks or mount points inside the legacy home produce non-normal path components; restructure or bind-mount cleanly.
Example fix
// before (legacy tree contains a non-canonical segment) /home/legacy-principal/.config/env//prod -> migration aborts // after (clean the legacy tree first) rm -r '/home/legacy-principal/.config/env//prod' # or rename so the relative path is canonical, then re-run migration
Defensive patterns
Strategy: validation
Validate before calling
fn is_canonical_relative(path: &std::path::Path) -> bool {
!path.is_absolute()
&& path.components().all(|c| matches!(c, std::path::Component::Normal(_)))
&& path.to_str().map(|t| t.replace('\\', "/").len() <= MAX_RELATIVE_PATH_BYTES).unwrap_or(false)
}
// pre-check each discovered legacy path before migration
if !is_canonical_relative(&legacy_path) { skip_or_repair(&legacy_path); } Type guard
fn is_utf8_canonical(p: &std::path::Path) -> Option<String> {
p.to_str().map(|t| t.replace('\\', "/")).filter(|t|
astrid_storage::FilesystemPath::new(t.clone()).is_ok()
)
} Try / catch
match migrate_legacy_principal_homes(&home, &fs, &source) {
Err(e) if e.kind() == std::io::ErrorKind::InvalidData
&& e.to_string().contains("not a canonical filesystem path") => {
eprintln!("repair legacy path, then retry: {e}");
}
other => other?,
} Prevention
- Keep legacy homes free of symlinks, '.', '..', and non-UTF-8 file names before migrating.
- Normalize separators to '/' and validate every relative path with FilesystemPath::new in a dry-run pass first.
- Enforce the MAX_RELATIVE_PATH_BYTES limit when creating legacy content, not only at migration time.
- Run the migration on a copied snapshot of the legacy home so repairs don't touch live data.
When it happens
Trigger: Calling migrate_legacy_principal_homes (directly or via walk_directory / retire_one_receipted_source) when a file or directory discovered under a legacy principal home produces a relative path that FilesystemPath::new rejects — e.g. paths with unusual separators left after the backslash normalization, embedded components astrid-storage considers non-canonical, or paths violating storage-layer canonical form rules not caught by the earlier checks.
Common situations: Migrating legacy homes whose on-disk trees were created by older or non-conforming tooling: symlinks resolved into odd forms, names with escaped or mixed separators, zero-length segments after normalization, or filesystems that permitted names the canonical storage model disallows.
Understand the failure class
Background: "Invalid ... format", "must be in format X", "does not look like a ..." — invalid argument format errors across CLI tools and libraries — this error's family across 17 libraries.
Related errors
- legacy principal-home source {}: {detail}
- invalid logical destination {}: {error}
- legacy audit source has no parent
- WouldBlock
- legacy audit retirement source is outside the default princi
AI-assisted analysis of astrid-runtime/astrid@affd8760f4 (2026-09-09).
Data as JSON: /api/errors/7d3a4e141f89f0a8.
Report an issue: GitHub.