astrid-runtime/astrid · error

legacy state source contains a redirect: {}

Error message

legacy state source contains a redirect: {}

What it means

Thrown when an entry inside the legacy state source tree is a symlink. The retirement path is strictly no-follow: every child is checked with symlink_metadata before being validated or deleted, and any redirect is rejected so deletion can never act through a link onto an outside target.

Source

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

            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()
                ),
            ));
        }
        ensure_legacy_tree_boundary(&child, root_device, &child_metadata)?;
        if child_metadata.is_dir() {
            validate_legacy_tree(&child, root_device)?;
        } else if child_metadata.is_file() {
            // Opening only after the no-follow validation ensures a replaced
            // symlink is rejected rather than read or removed through it.
            crate::platform_fs::verify_no_redirects(&child)?;
        } else {
            return Err(io::Error::new(
                io::ErrorKind::InvalidData,
                format!(

View on GitHub (pinned to affd8760f4)

Solutions

  1. Remove the symlinked entries and copy/move the real data into the tree so every entry is a regular file or directory (bind mounts are also rejected at boundaries, so use physical copies)
  2. Check whether an earlier relocation or disk-space workaround created the links; undo that layout
  3. Verify no external tooling (backups, cleanup cron jobs) reintroduces symlinks, then re-run retirement

Example fix

// before: linked data file inside the legacy tree
/srv/astrid/var/state/sstables/00000000000000000001.sst -> /mnt/big/00000000000000000001.sst
// after: place a real copy in the tree
rm /srv/astrid/var/state/sstables/00000000000000000001.sst
cp /mnt/big/00000000000000000001.sst /srv/astrid/var/state/sstables/
Defensive patterns

Strategy: validation

Validate before calling

fn tree_has_symlinks(root: &Path) -> std::io::Result<Vec<std::path::PathBuf>> {
    let mut found = Vec::new();
    for entry in std::fs::read_dir(root)? {
        let child = entry?.path();
        if std::fs::symlink_metadata(&child)?.file_type().is_symlink() {
            found.push(child);
        } else if child.is_dir() {
            found.extend(tree_has_symlinks(&child)?);
        }
    }
    Ok(found)
}

Type guard

fn is_redirect(path: &Path) -> bool {
    std::fs::symlink_metadata(path)
        .map(|m| m.file_type().is_symlink())
        .unwrap_or(false)
}

Try / catch

match retire_legacy_source_tree(&path) {
    Err(e) if e.to_string().contains("contains a redirect") => {
        // parse offending child from the message, replace symlink with real data
    }
    other => other?,
}

Prevention

When it happens

Trigger: validate_legacy_tree enumerates the tree and finds child_metadata.file_type().is_symlink() for any child — e.g. a symlinked data file (sstables, wal, manifest), a linked subdirectory, or a convenience link created by tooling inside the legacy state directory.

Common situations: Administrators symlinking large WAL/SSTable files onto another volume to save space; moving part of the tree and linking it back; backup tools leaving symlink placeholders; restore from a layout that used links.

Understand the failure class

Background: Path traversal blocked: "path escapes the workspace" and "outside site root" errors when a path will not stay inside its allowed directory — this error's family across 26 libraries.

Related errors


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