astrid-runtime/astrid · error

workspace path redirects from its selected target

Error message

workspace path redirects from its selected target: {}

What it means

As a final per-component check, resolve_descendant canonicalizes the current path and requires it to equal itself; if canonicalization yields a different path the route 'redirects' from its selected target and InvalidInput is raised. This catches cases the metadata checks miss, such as case-insensitive filesystem aliases or hardlink/bind tricks that make the walked path differ from the kernel's true path.

Solutions

  1. Use the canonicalized path (dunce::canonicalize / fs::canonicalize output) when constructing descendant paths so walked and canonical forms match.
  2. Fix directory-name casing to exactly match the on-disk names.
  3. Remove bind/overlay mounts or symlinked parents inside the workspace.
  4. Re-run resolution if a concurrent process mutated the tree; serialize workspace mutations to avoid TOCTOU races.

Example fix

// before
let f = ws.resolve_file(Path::new("Config/Settings.TOML"))?; // on-disk: config/settings.toml
// after
let canonical = std::fs::canonicalize(root)?;
let rel = Path::new("config/settings.toml");
let f = ws.resolve_file(rel)?;
Defensive patterns

Strategy: validation

Validate before calling

let canonical = std::fs::canonicalize(&root)?;
let target = canonical.join(rel);
debug_assert_eq!(std::fs::canonicalize(&target).unwrap(), target, "path redirects");

Prevention

When it happens

Trigger: resolve_directory or resolve_file walking a path where any component's canonical form differs from the walked form — typically due to symlinked parents that passed earlier checks only via TOCTOU, bind mounts, or case-insensitive filesystem mismatches.

Common situations: macOS/Windows case-insensitive volumes where the stored path casing differs from the on-disk casing; a parent directory swapped for a symlink between the metadata check and canonicalize; overlay/bind mounts inside the workspace.

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/8a207ecf87860239. Report an issue: GitHub.

Appendix: source

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

                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))
    }

    /// Re-check that the selected state path has not been redirected.
    ///
    /// A missing state directory remains valid. This permits a checked
    /// selection to be created before initialization while still rejecting a
    /// later symlink or non-directory replacement.
    ///
    /// # Errors

View on GitHub (pinned to affd8760f4)