{"record":{"id":"1863cb449d9d0590","repo":"astral-sh/ruff","slug":"notebook-document-path-does-not-point-to-a-noteboo","errorCode":null,"errorMessage":"Notebook document path does not point to a notebook document","messagePattern":"Notebook document path does not point to a notebook document","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"crates/ty_server/src/session/index.rs","lineNumber":76,"sourceCode":"    #[expect(dead_code)]\n    fn notebook_document_keys(&self) -> impl Iterator<Item = &DocumentKey> + '_ {\n        self.documents\n            .iter()\n            .filter(|(_, doc)| doc.as_notebook().is_some())\n            .map(|(key, _)| key)\n    }\n\n    pub(super) fn update_notebook_document(\n        &mut self,\n        notebook_key: &DocumentKey,\n        cells: Option<lsp_types::NotebookDocumentCellChanges>,\n        metadata: Option<serde_json::Map<String, serde_json::Value>>,\n        new_version: DocumentVersion,\n        encoding: PositionEncoding,\n    ) -> crate::Result<()> {\n        let document = self.document_mut(notebook_key)?;\n        let Some(notebook) = document.as_notebook_mut() else {\n            anyhow::bail!(\"Notebook document path does not point to a notebook document\");\n        };\n\n        let (structure, data, text_content) = cells\n            .map(|cells| {\n                let lsp_types::NotebookDocumentCellChanges {\n                    structure,\n                    data,\n                    text_content,\n                } = cells;\n                (structure, data, text_content)\n            })\n            .unwrap_or_default();\n\n        let (array, did_open, did_close) = structure\n            .map(|structure| {\n                let lsp_types::NotebookDocumentCellChangeStructure {\n                    array,\n                    did_open,","sourceCodeStart":58,"sourceCodeEnd":94,"githubUrl":"https://github.com/astral-sh/ruff/blob/26f38c119cac42e4d320ba08f09224fdec74af2c/crates/ty_server/src/session/index.rs#L58-L94","documentation":"ty_server maintains one document per path in its session, which can be either a text document or a notebook document. update_notebook_document expects the document at notebook_key.uri to be a notebook; when it is stored as a plain text document (or vice versa), the downcast to a notebook fails and the server bails with this message.","triggerScenarios":"Calling session.update_notebook_document (e.g. from a notebookDocument/didChange LSP notification) for a URI whose entry in the session documents map was opened as a regular text document, or whose kind changed between didOpen and didChange.","commonSituations":"An LSP client opens the same .ipynb file both as a text document and as a notebook document, a client sends notebook change notifications for a path it never opened as a notebook, or editor tooling (diff views, virtual copies) creates a plain-text document at a notebook path.","solutions":["Ensure the client sends notebookDocument/didOpen (not textDocument/didOpen) for notebook URIs before sending notebook change events","Check the server's did_open/did_close handlers so a document is closed and re-registered as a notebook when its kind changes","Log/inspect the session's document kind at the given path before calling update_notebook_document","Return a typed error instead of anyhow::bail! so clients can react to the kind mismatch"],"exampleFix":"// before\nlet Some(notebook) = document.as_notebook_mut() else {\n    anyhow::bail!(\"Notebook document path does not point to a notebook document\");\n};\n// after\nlet Some(notebook) = document.as_notebook_mut() else {\n    return Err(crate::Error::new(\n        format!(\"document at {} is not a notebook (kind: {:?})\", notebook_key.uri, document.kind()),\n    ));\n};","handlingStrategy":"type-guard","validationCode":"fn is_notebook(session: &Session, uri: &Url) -> bool {\n    session.document(uri).map(|d| d.as_notebook().is_some()).unwrap_or(false)\n}\n// call update_notebook_document only if is_notebook(...)\n","typeGuard":"fn as_notebook(document: &Document) -> Option<&Notebook> {\n    document.as_notebook()\n}","tryCatchPattern":"if let Err(err) = session.update_notebook_document(...) {\n    if err.to_string().contains(\"does not point to a notebook document\") {\n        // fall back to plain-text document update path\n    }\n}","preventionTips":["Match LSP document kind: only send notebook change events for URIs opened via notebookDocument/didOpen","Close and re-open documents when their kind changes on the client side","On the server, route updates based on document kind instead of the notification type alone"],"tags":["lsp","notebook","rust","state-mismatch"],"backgroundTag":"document-kind-mismatch","analyzedSha":"26f38c119cac42e4d320ba08f09224fdec74af2c","analyzedAt":"2026-09-05T10:32:37.492Z","contentChangedAt":"2026-09-05T10:32:37.492Z","schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}