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

InvalidInput

InvalidInput

Error message

Input is not a text document: {path}

What it means

The ty server resolves virtual paths (untitled documents, notebook cells) through the LSP document index. read_virtual_path_to_string looks the path up and requires the stored Document to be a text document; if it is a Notebook document it returns ErrorKind::InvalidInput with 'Input is not a text document: {path}'.

Source

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

struct UntrustedWorkspaceExecutor;

impl CommandExecutor for UntrustedWorkspaceExecutor {
    fn execute(&self, _command: Command) -> Result<Output> {
        Err(std::io::Error::new(
            std::io::ErrorKind::PermissionDenied,
            "external commands are disabled in an untrusted workspace",
        ))
    }

    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) => {

View on GitHub (pinned to d1087a4b9e)

Solutions

  1. Use notebook-aware reads (read_to_notebook) for notebook documents instead of read_to_string
  2. Inspect the document kind in the index before choosing the read API
  3. Ensure the client's notebookDocument capabilities match how the documents are actually synced
Defensive patterns

Strategy: validation

Validate before calling

// Before reading a virtual path, confirm the synced document kind matches the read API
let expected_text = matches!(index.document_for(path), Some(Document::Text(_)));
if !expected_text { use_notebook_read_instead(); }

Type guard

fn is_text_document(doc: &Document) -> bool { matches!(doc, Document::Text(_)) }

Try / catch

match system.read_virtual_path_to_string(path) {
    Ok(s) => { /* use s */ }
    Err(e) if e.kind() == std::io::ErrorKind::InvalidInput => { /* re-read as notebook or skip */ }
    Err(e) => return Err(e),
}

Prevention

When it happens

Trigger: A string-oriented read hits a virtual path whose open LSP document is a NotebookDocument instead of a TextDocument - e.g. notebook cell paths read via read_to_string while the editor syncs that document as a notebook.

Common situations: Editors that sync Jupyter files as NotebookDocuments (VS Code Jupyter) while a code path assumes plain text; notebook-document support toggling between ty versions; stale assumptions about how a path is synced.

Related errors


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