astral-sh/ruff · error · std::io::Error

NotFound

NotFound

Error message

Virtual path does not exist: {path}

What it means

Virtual paths only exist while their document is open in the LSP index. read_virtual_path_to_string/read_virtual_path_to_notebook call system_virtual_path_to_document and, when no open document matches, return ErrorKind::NotFound with 'Virtual path does not exist: {path}'.

Source

Thrown at crates/ty_server/src/system.rs:347

        ))
    }

    fn dyn_clone(&self) -> Box<dyn CommandExecutor> {
        Box::new(Self)
    }
}

fn not_a_text_document(path: impl Display) -> std::io::Error {
    std::io::Error::new(
        std::io::ErrorKind::InvalidInput,
        format!("Input is not a text document: {path}"),
    )
}

fn virtual_path_not_found(path: impl Display) -> std::io::Error {
    std::io::Error::new(
        std::io::ErrorKind::NotFound,
        format!("Virtual path does not exist: {path}"),
    )
}

/// Helper function to get the [`FileRevision`] of the given document.
fn document_revision(document: &Document, index: &Index) -> FileRevision {
    // The file revision is just an opaque number which doesn't have any significant meaning other
    // than that the file has changed if the revisions are different.
    #[expect(clippy::cast_sign_loss)]
    match document {
        Document::Text(text) => FileRevision::new(text.version() as u128),
        Document::Notebook(notebook) => {
            // VS Code doesn't always bump the notebook version when the cell content changes.
            // Specifically, I noticed that VS Code re-uses the same version when:
            // 1. Adding a new cell
            // 2. Pasting some code that has an error
            //
            // The notification updating the cell content on paste re-used the same version as when the cell was added.
            // Because of that, hash all cell versions and the notebook versions together.

View on GitHub (pinned to d1087a4b9e)

Solutions

  1. Ensure textDocument/didOpen (or notebook document sync) completes before issuing requests that touch the document
  2. Cancel or discard work for documents that receive didClose
  3. Validate the virtual path against the open-document index before reading
Defensive patterns

Strategy: validation

Validate before calling

// Issue requests only while the document is open
if (!openDocuments.has(virtualPath)) {
  await connection.sendRequest('textDocument/didOpen' /* ensure open first */);
}

Try / catch

match system.read_virtual_path_to_string(path) {
    Ok(text) => { /* use text */ }
    Err(e) if e.kind() == std::io::ErrorKind::NotFound => { /* document closed: drop the stale work item */ }
    Err(e) => return Err(e),
}

Prevention

When it happens

Trigger: ty reads a virtual path (untitled file, notebook cell) after the client sent didClose, before the first didOpen, or for a document that was never synced by this client.

Common situations: In-flight requests racing a document close (hover finishing after close); editors that skip syncing untitled files; retries against a restarted server holding stale paths.

Understand the failure class

Background: "File not found" and ENOENT errors: why libraries can't find a file that should exist — this error's family across 50 libraries.

Related errors


AI-assisted analysis of astral-sh/ruff@d1087a4b9e (2026-08-20). Data as JSON: /api/errors/a7b8f683242c7521. Report an issue: GitHub.