astrid-runtime/astrid · error
legacy state source changed type: {}
Error message
legacy state source changed type: {} What it means
Thrown by delete_legacy_tree when, at deletion time, the legacy state source path is no longer the expected real directory: it is a symlink or some non-directory entry. This guards against the tree changing type between validation and deletion so removal never acts on the wrong object. The error is a deliberate safety stop — nothing in the tree is deleted.
Source
Thrown at crates/astrid-core/src/dirs_layout_retirement.rs:97
io::ErrorKind::InvalidData,
format!(
"legacy state source contains a special file: {}",
child.display()
),
));
}
}
Ok(())
}
pub(super) fn delete_legacy_tree(path: &Path, root_device: u64) -> io::Result<()> {
let metadata = match std::fs::symlink_metadata(path) {
Ok(metadata) => metadata,
Err(error) if error.kind() == io::ErrorKind::NotFound => return Ok(()),
Err(error) => return Err(error),
};
if metadata.file_type().is_symlink() || !metadata.is_dir() {
return Err(io::Error::new(
io::ErrorKind::InvalidData,
format!("legacy state source changed type: {}", path.display()),
));
}
crate::platform_fs::verify_no_redirects(path)?;
ensure_legacy_tree_boundary(path, root_device, &metadata)?;
for entry in std::fs::read_dir(path)? {
let entry = entry?;
let child = entry.path();
let child_metadata = std::fs::symlink_metadata(&child)?;
if child_metadata.file_type().is_symlink() {
return Err(io::Error::new(
io::ErrorKind::InvalidData,
format!(
"legacy state source contains a redirect: {}",
child.display()
),View on GitHub (pinned to affd8760f4)
Solutions
- Re-run retirement with nothing else touching the data directory; it is idempotent and re-validates from scratch
- Stop other processes (second instances, migration jobs, backup agents) that may be mutating the path concurrently, then retry
- Inspect the path and restore the real directory layout if something actually replaced it
- Serialize retirement/migration steps so validation and deletion run without an interleaved swap
Example fix
// before: racing migration swaps the path mid-retirement
mv /srv/astrid/var/state /srv/astrid/var/state.new &
retire_legacy_source_tree("/srv/astrid/var/state") // InvalidData: changed type
// after: perform moves and retirement sequentially, with the app stopped
mv /srv/astrid/var/state /srv/astrid/var/state.new
retire_legacy_source_tree("/srv/astrid/var/state") Defensive patterns
Strategy: try-catch
Validate before calling
fn recheck_before_delete(path: &Path) -> std::io::Result<()> {
let meta = std::fs::symlink_metadata(path)?;
if meta.file_type().is_symlink() || !meta.is_dir() {
return Err(std::io::Error::new(
std::io::ErrorKind::InvalidData,
"state path changed type",
));
}
Ok(())
} Type guard
fn still_real_dir(path: &Path) -> bool {
std::fs::symlink_metadata(path)
.map(|m| m.is_dir() && !m.file_type().is_symlink())
.unwrap_or(false)
} Try / catch
match retire_legacy_source_tree(&path) {
Err(e) if e.kind() == std::io::ErrorKind::InvalidData && e.to_string().contains("changed type") => {
// quiesce concurrent writers, verify layout, retry retirement
}
other => other?,
} Prevention
- Stop all instances and migration jobs before retiring the legacy tree
- Serialize path-mutating operations (mv/ln/rm) with retirement under a lock or maintenance window
- Treat this error as a race indicator: investigate concurrent access before simply retrying
When it happens
Trigger: retire_legacy_source_tree validated the tree, but before/while delete_legacy_tree re-stats it, the path was replaced by a symlink or a file; also if delete_legacy_tree is invoked directly with a bad path. Any race that swaps the directory entry (mv, ln -s, rm + create) during retirement triggers it.
Common situations: Concurrent deployment/migration scripts moving or linking the state dir during a retirement run; two astrid instances racing on the same data directory; operator intervention mid-migration; flaky automation that recreates the path.
Understand the failure class
Background: "Invalid state transition" errors: "status must be X, actually Y", "already rejected/charging/uninstalled", "cannot ... while running" — what they mean when a library rejects your call — this error's family across 31 libraries.
Related errors
- legacy state source is redirected: {}
- legacy state source contains a redirect: {}
- legacy source changed while opening: {}
- principal-store directory changed while it was opened
- opened representation entry is redirected or not a regular f
AI-assisted analysis of astrid-runtime/astrid@affd8760f4 (2026-09-09).
Data as JSON: /api/errors/8c544680bd11df0c.
Report an issue: GitHub.