astrid-runtime/astrid · error
legacy state source is not a directory: {}
Error message
legacy state source is not a directory: {} What it means
Thrown by validate_legacy_tree when the legacy state source path exists but is a regular file, FIFO, or other non-directory entry instead of the expected directory. The retirement machinery only knows how to validate and delete directory trees, so it refuses with InvalidData rather than proceeding.
Source
Thrown at crates/astrid-core/src/dirs_layout_retirement.rs:49
metadata.dev()
}
#[cfg(not(unix))]
pub(super) fn legacy_tree_device(_metadata: &std::fs::Metadata) -> u64 {
0
}
pub(super) fn validate_legacy_tree(path: &Path, root_device: u64) -> io::Result<()> {
let metadata = std::fs::symlink_metadata(path)?;
if metadata.file_type().is_symlink() {
return Err(io::Error::new(
io::ErrorKind::InvalidData,
format!("legacy state source is redirected: {}", path.display()),
));
}
if !metadata.is_dir() {
return Err(io::Error::new(
io::ErrorKind::InvalidData,
format!("legacy state source is not a directory: {}", 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
- Inspect the path (ls -la / stat) and determine what the non-directory entry is
- Remove or rename the stray file and restore the real state directory contents there
- Re-run retirement after the path is a real directory again
- Check for concurrent writers (another astrid instance, migration job) that may be swapping the entry
Example fix
// before: state path holds a stray file /srv/astrid/var/state (regular file, 42 bytes) // after: remove the stray file and restore the directory mv /srv/astrid/var/state /srv/astrid/var/state.stray mkdir -p /srv/astrid/var/state # restore contents from backup
Defensive patterns
Strategy: validation
Validate before calling
fn ensure_state_dir(path: &Path) -> std::io::Result<()> {
let meta = std::fs::symlink_metadata(path)?;
if !meta.is_dir() {
return Err(std::io::Error::new(
std::io::ErrorKind::InvalidData,
format!("{} must be a real directory", path.display()),
));
}
Ok(())
} Type guard
fn is_plain_directory(path: &Path) -> bool {
std::fs::symlink_metadata(path)
.map(|m| m.is_dir())
.unwrap_or(false)
} Try / catch
match retire_legacy_source_tree(&path) {
Err(e) if e.kind() == std::io::ErrorKind::InvalidData && e.to_string().contains("not a directory") => {
// inspect entry, restore directory contents, retry
}
other => other?,
} Prevention
- Verify the state path is a directory after every restore or migration
- Don't write placeholder/marker files into configured state paths
- Ensure only one process owns lifecycle operations on the state tree
When it happens
Trigger: validate_legacy_tree (via validate_legacy_retirement_candidate or retire_legacy_source_tree) sees symlink_metadata report is_dir() == false for the source path. Occurs if the state path was replaced by a plain file, a truncated restore left a file where the directory was, or a race changed the entry type after the caller's initial check.
Common situations: Backup restores that recreated state as a tar file left in place; scripts that wrote a marker file at the state path; migration tooling that deleted the directory and wrote a placeholder; concurrent processes swapping the path mid-retirement.
Related errors
- read capsule projection {}: {error}
- read capsule projection entry: {error}
- inspect capsule projection {}: {error}
- inspect projected file {}: {error}
- read projected file {}: {error}
AI-assisted analysis of astrid-runtime/astrid@affd8760f4 (2026-09-09).
Data as JSON: /api/errors/a0abad66b7d17edd.
Report an issue: GitHub.