astrid-runtime/astrid · error

release executable is redirected or not regular: {name}

Error message

release executable is redirected or not regular: {name}

What it means

For each executable, `validate_replacement_inputs` inspects `symlink_metadata` of the staged source. If the entry is a symlink or not a regular file (directory, FIFO, device, etc.), the call fails with `InvalidInput` — the library refuses to install executables that are redirects, which could silently point installations at attacker-controlled or unintended targets.

Source

Thrown at crates/astrid-core/src/platform_fs.rs:859

        if !matches!(components.next(), Some(Component::Normal(_)))
            || components.next().is_some()
            || !unique.insert(*name)
        {
            return Err(io::Error::new(
                io::ErrorKind::InvalidInput,
                format!("invalid or duplicate executable name '{name}'"),
            ));
        }

        let source = extract_dir.join(name);
        let metadata = std::fs::symlink_metadata(&source).map_err(|error| {
            io::Error::new(
                error.kind(),
                format!("release archive is missing '{name}': {error}"),
            )
        })?;
        if metadata.file_type().is_symlink() || !metadata.is_file() {
            return Err(io::Error::new(
                io::ErrorKind::InvalidInput,
                format!("release executable is redirected or not regular: {name}"),
            ));
        }
    }
    Ok(())
}

#[cfg(not(windows))]
fn replace_executable_set_by_rename(
    install_dir: &Path,
    extract_dir: &Path,
    names: &[&str],
) -> io::Result<()> {
    let mut backups = Vec::new();
    for name in names {
        let live = install_dir.join(name);
        if live.exists() {

View on GitHub (pinned to affd8760f4)

Solutions

  1. Extract the archive with symlinks dereferenced/converted to real copies (e.g. `tar --dereference`) so each name is a regular file
  2. Rebuild the release archive so executables are regular files, not symlinks
  3. Point `extract_dir` at the real binary location, not a symlinked wrapper path; check each entry with `symlink_metadata(...).is_file()` before calling

Example fix

// before
Command::new("tar").args(["-xzf", archive, "-C", extract_dir]).status()?;
// after
Command::new("tar")
    .args(["--dereference", "-xzf", archive, "-C", extract_dir])
    .status()?;
Defensive patterns

Strategy: validation

Validate before calling

for name in names {
    let md = std::fs::symlink_metadata(extract_dir.join(name))?;
    if md.file_type().is_symlink() || !md.is_file() {
        return Err(anyhow!("{name} is not a regular file"));
    }
}

Type guard

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

Try / catch

match replace_executable_set(&install_dir, &extract_dir, names) {
    Err(e) if e.to_string().starts_with("release executable is redirected") => {
        eprintln!("archive contains symlinks; re-extract with --dereference: {e}");
    }
    other => other.map_err(Into::into),
}

Prevention

When it happens

Trigger: The extracted archive contains a symlink (common on Unix for versioned binaries like `astrid -> astrid-1.2.3`), or `extract_dir.join(name)` resolves to a directory/pipe rather than a regular file; also when an extraction step preserved symlinks from a tarball.

Common situations: Unix archives (.tar.gz) built with symlinks inside; a malicious or mis-built release archive; extraction tools that create wrapper symlinks; a directory in extract_dir sharing the executable's name.

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/198de921c4ffb094. Report an issue: GitHub.