astrid-runtime/astrid · error · io::Error

legacy state source is redirected or not a directory: {}

Error message

legacy state source is redirected or not a directory: {}

What it means

Before retiring the legacy directory-backed state tree, validate_legacy_retirement_candidate checks the configured legacy path with symlink_metadata: if it is a symlink (redirect) or not a real directory, the layout-v2 preflight refuses to proceed with InvalidData. This prevents migrating/retiring data that a symlink silently redirects elsewhere, or a path that never held the legacy state at all.

Source

Thrown at crates/astrid-core/src/dirs_layout_retirement.rs:17

//! No-follow retirement of the released directory-backed state tree.

#[cfg(unix)]
use std::fs::File;
use std::io;
use std::path::Path;
#[cfg(target_os = "linux")]
use std::path::PathBuf;

pub(super) fn validate_legacy_retirement_candidate(path: &Path) -> 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 is redirected or not a directory: {}",
                path.display()
            ),
        ));
    }
    validate_legacy_tree(path, legacy_tree_device(&metadata))
}

#[cfg(unix)]
pub(super) fn legacy_tree_device(metadata: &std::fs::Metadata) -> u64 {
    use std::os::unix::fs::MetadataExt as _;

    metadata.dev()
}

#[cfg(not(unix))]

View on GitHub (pinned to affd8760f4)

Solutions

  1. Remove the symlink and move the real state directory to the configured path (cp -a the target's contents, then delete the link).
  2. Fix the configuration so the legacy state path points at the actual directory, not a file or a link.
  3. Verify with: stat <path> — it must show a directory, not 'symbolic link'.
  4. If there is genuinely no legacy state, ensure the path does not exist at all (the check passes on NotFound).

Example fix

// before
ln -s /mnt/data/astrid-state ~/.local/state/astrid
// after
rm ~/.local/state/astrid
mv /mnt/data/astrid-state ~/.local/state/astrid   # real directory at configured path
Defensive patterns

Strategy: validation

Validate before calling

use std::path::Path;
fn legacy_path_ok(path: &Path) -> bool {
    match std::fs::symlink_metadata(path) {
        Ok(m) => !m.file_type().is_symlink() && m.is_dir(),
        Err(e) if e.kind() == std::io::ErrorKind::NotFound => true, // no legacy state is fine
        Err(_) => false,
    }
}

Type guard

fn is_plain_directory(p: &Path) -> bool {
    std::fs::symlink_metadata(p).map(|m| !m.file_type().is_symlink() && m.is_dir()).unwrap_or(false)
}

Try / catch

match result {
    Err(e) if e.to_string().contains("redirected or not a directory") => {
        eprintln!("fix the configured legacy state path (no symlinks, must be a dir)");
        return Err(e);
    }
    other => other,
}

Prevention

When it happens

Trigger: Calling preflight_layout_v2_paths / validate_layout_v2_legacy_sources when the legacy state path either is a symlink or resolves to a non-directory (file, socket, etc.). A NotFound path is tolerated (no legacy state), but anything else non-directory fails.

Common situations: Dotfile managers (stow/dotbot) symlinked the state directory; the path was repointed to a file (e.g. a tarball or a moved database file); a config mistake points ASTRID at the wrong path; the state dir was replaced by a mount target that is not a directory.

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


AI-assisted analysis of astrid-runtime/astrid@affd8760f4 (2026-09-09). Data as JSON: /api/errors/230f11953f701249. Report an issue: GitHub.