astrid-runtime/astrid · error

workspace path must not contain redirects or unexpected file

Error message

workspace path must not contain redirects or unexpected file types: {}

What it means

While walking each component of the descendant path, resolve_descendant stats each intermediate/current path and rejects it with InvalidInput if it is a symlink, if the final component is not a regular file when a file was requested, or if any non-final component is not a directory. This enforces that the resolved route contains no redirects and matches the requested kind.

Source

Thrown at crates/astrid-core/src/workspace_security.rs:199

        let mut current = self.state_dir.clone();
        for (index, component) in components.iter().enumerate() {
            let Component::Normal(component) = component else {
                unreachable!("components validated above")
            };
            current.push(component);
            let metadata = match std::fs::symlink_metadata(&current) {
                Ok(metadata) => metadata,
                Err(error) if error.kind() == io::ErrorKind::NotFound => continue,
                Err(error) => return Err(error),
            };
            let final_component = index == components.len().saturating_sub(1);
            let expected_file = final_component && kind == DescendantKind::File;
            if metadata.file_type().is_symlink()
                || (expected_file && !metadata.is_file())
                || (!expected_file && !metadata.is_dir())
            {
                return Err(io::Error::new(
                    io::ErrorKind::InvalidInput,
                    format!(
                        "workspace path must not contain redirects or unexpected file types: {}",
                        current.display()
                    ),
                ));
            }
            if std::fs::canonicalize(&current)? != current {
                return Err(io::Error::new(
                    io::ErrorKind::InvalidInput,
                    format!(
                        "workspace path redirects from its selected target: {}",
                        current.display()
                    ),
                ));
            }
        }
        Ok(self.state_dir.join(relative))

View on GitHub (pinned to affd8760f4)

Solutions

  1. Ensure the final component's type matches the call: use resolve_file for files and resolve_directory for directories.
  2. Replace symlinks inside the workspace with real files/directories.
  3. Check what exists at the reported path (ls -la) and reconcile it with what the code expects.
  4. If symlinked config is intentional, copy the file into the workspace instead of linking it.

Example fix

// before
let f = ws.resolve_file(Path::new("config/settings.toml"))?; // settings.toml is actually a directory
// after
let meta = std::fs::metadata(root.join("config/settings.toml"))?;
let entry = if meta.is_file() {
    ws.resolve_file(Path::new("config/settings.toml"))?
} else {
    ws.resolve_directory(Path::new("config/settings.toml"))?
};
Defensive patterns

Strategy: validation

Validate before calling

fn entry_matches(p: &Path, want_file: bool) -> bool {
    match std::fs::metadata(p) {
        Ok(m) => !m.file_type().is_symlink() && if want_file { m.is_file() } else { m.is_dir() },
        Err(_) => false,
    }
}

Prevention

When it happens

Trigger: resolve_file called on a path whose final component is a directory (or vice versa resolve_directory on a file); any component of the path being a symlink; intermediate components that are regular files (e.g. treating "a.txt/b" style paths).

Common situations: Caller assuming a file exists but a directory with the same name is present (or the file was replaced by a symlink); symlinked dotfile setups (e.g. dotfiles managed with symlinks into the workspace); race where another process swaps a directory for a symlink mid-walk.

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/53f01f0c08817a97. Report an issue: GitHub.