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

representation namespace entry is redirected or has the…

Error message

representation namespace entry is redirected or has the wrong type

What it means

reject_redirect inspects symlink_metadata of a namespace entry and rejects it if it is a symlink/redirect or has the wrong type (directory expected when directory=true, regular file otherwise). This InvalidData error guards the contiguous-representation namespace against redirected or mistyped entries. It runs before each directory open in open_component.

Solutions

  1. Replace the symlink or mistyped entry with the correct object type (real directory or regular file)
  2. Do not create symlinks inside the representation namespace
  3. Fix the migration/tooling script that produced the wrong entry type
  4. Tighten store-directory permissions against third-party modification

Example fix

// before
ln -s ../other-store/rep store/rep/abc
// after
rm store/rep/abc && mkdir store/rep/abc
Defensive patterns

Strategy: validation

Validate before calling

fn check_entry_type(p: &Path, want_dir: bool) -> io::Result<()> {
    let md = std::fs::symlink_metadata(p)?;
    let ok = if want_dir { md.is_dir() } else { md.is_file() };
    if !ok || md.file_type().is_symlink() {
        return Err(io::Error::new(io::ErrorKind::InvalidData, "redirected or wrong type"));
    }
    Ok(())
}

Type guard

fn is_correct_entry(p: &Path, want_dir: bool) -> bool {
    std::fs::symlink_metadata(p)
        .map(|m| !m.file_type().is_symlink() && (if want_dir { m.is_dir() } else { m.is_file() }))
        .unwrap_or(false)
}

Try / catch

match open_result {
    Err(e) if e.kind() == io::ErrorKind::InvalidData => {
        // fix entry type / remove symlink, then retry
    }
    r => r,
}

Prevention

When it happens

Trigger: Calling open_component (and thereby reject_redirect) on an entry that is a symlink, or a file where a directory is expected (or vice versa); also triggered by filesystem redirection detected by is_redirect.

Common situations: Symlinking namespace entries to share representations between stores; a migration script created files where directories belong; tampering in a shared store directory.

Related errors


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

Appendix: source

Thrown at crates/astrid-storage/src/engine/durable/representations/contiguous/namespace.rs:94

    #[cfg(unix)]
    {
        directory.open(Path::new("."))?.into_std().sync_all()
    }

    #[cfg(not(unix))]
    {
        let _ = directory;
        Ok(())
    }
}

fn reject_redirect(parent: &Dir, name: &Path, directory: bool) -> io::Result<()> {
    let metadata = parent.symlink_metadata(name)?;
    if is_redirect(&metadata)
        || (directory && !metadata.is_dir())
        || (!directory && !metadata.is_file())
    {
        return Err(io::Error::new(
            io::ErrorKind::InvalidData,
            "representation namespace entry is redirected or has the wrong type",
        ));
    }
    Ok(())
}

pub(in crate::engine::durable::representations) fn configure_no_follow(options: &mut OpenOptions) {
    #[cfg(unix)]
    {
        use cap_std::fs::OpenOptionsExt as _;
        options.custom_flags(libc::O_NOFOLLOW | libc::O_NONBLOCK);
    }
    #[cfg(windows)]
    {
        use cap_std::fs::OpenOptionsExt as _;
        use windows_sys::Win32::Storage::FileSystem::FILE_FLAG_OPEN_REPARSE_POINT;
        options.custom_flags(FILE_FLAG_OPEN_REPARSE_POINT);

View on GitHub (pinned to affd8760f4)