Hmbown/CodeWhale · error · io::Error

LSP file is outside the workspace

Error message

LSP file is outside the workspace

What it means

The LSP workspace file reader confines every requested path to the workspace root. It tries to strip the workspace root (or its canonicalized form) from the requested absolute path; if the path is not under either, it raises PermissionDenied with this message. This is a path-traversal guard preventing LSP document requests from reading files outside the workspace.

Solutions

  1. Ensure the requested path resolves under the workspace root; use path joining relative to root instead of ../ escapes.
  2. Canonicalize your path first and verify it starts with the workspace root before calling.
  3. If the file genuinely lives elsewhere, add that directory to the workspace or open a separate workspace rooted there.

Example fix

// before
read_workspace_text("/other/repo/src/main.rs")
// after
let p = root.join("src/main.rs").canonicalize()?; // inside workspace
read_workspace_text(&p)
Defensive patterns

Strategy: validation

Validate before calling

let root = root.canonicalize()?;
let p = requested.canonicalize()?;
if !p.starts_with(&root) { eprintln!("refusing: outside workspace"); }

Type guard

fn in_workspace(root: &Path, p: &Path) -> bool {
    p.canonicalize().map(|c| c.starts_with(root)).unwrap_or(false)
}

Try / catch

match read_workspace_text(p) {
    Err(e) if e.kind() == std::io::ErrorKind::PermissionDenied => eprintln!("outside workspace: {p:?}"),
    Err(e) => return Err(e),
    Ok(text) => use_text(text),
}

Prevention

When it happens

Trigger: Calling read_workspace_text (directly or via diagnostics_for, diagnostics_for_custom, intelligence_at_revision, semantic_locations, diagnostics_for_paths) with an absolute path that is not under the workspace root or its canonical root.

Common situations: Passing a path containing ../ segments that escape the workspace; requesting a file from a sibling project; the LSP client sending a document URI that was resolved through a symlink outside the root.

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 Hmbown/CodeWhale@73e0f67d83 (2026-09-22). Data as JSON: /api/errors/4a26216331e626c1. Report an issue: GitHub.

Appendix: source

Thrown at crates/tui/src/lsp/mod.rs:228

    pub fn config(&self) -> &LspConfig {
        &self.config
    }

    async fn read_workspace_text(&self, file: &Path) -> std::io::Result<String> {
        let root = self.workspace.clone();
        let requested = file.to_path_buf();
        tokio::task::spawn_blocking(move || {
            use std::io::Read;
            // The caller may already have resolved the configured workspace
            // alias (macOS /var -> /private/var, or a workspace-root symlink).
            // Resolve only that authorized root, never the requested file:
            // internal links must still be rejected by the confined opener.
            let canonical_root = root.canonicalize()?;
            let relative = requested
                .strip_prefix(&root)
                .or_else(|_| requested.strip_prefix(&canonical_root))
                .map_err(|_| {
                    std::io::Error::new(
                        std::io::ErrorKind::PermissionDenied,
                        "LSP file is outside the workspace",
                    )
                })?;
            // Match the existing workspace file serving ceiling. Decode only
            // bounded UTF-8 bytes from the same no-follow workspace opener.
            const MAX_DOCUMENT_BYTES: u64 = 16 * 1024 * 1024;
            let file = crate::fleet::files::WorkspaceFile::open(&canonical_root, relative, false)?;
            let mut bytes = Vec::new();
            file.open_file()?
                .take(MAX_DOCUMENT_BYTES + 1)
                .read_to_end(&mut bytes)?;
            if bytes.len() as u64 > MAX_DOCUMENT_BYTES {
                return Err(std::io::Error::new(
                    std::io::ErrorKind::InvalidData,
                    "LSP file exceeds the document limit",
                ));
            }

View on GitHub (pinned to 73e0f67d83)