astral-sh/ruff · error

Tried to close unavailable document

Error message

Tried to close unavailable document `{key}`

What it means

`close_document` removes a document from the session index on `textDocument/didClose`. If the key has no indexed URI, the server cannot locate the document to remove and bails with this message. Notebook cells are handled earlier and excluded from this path.

Solutions

  1. Ensure each didClose corresponds to a previously sent didOpen for the same URI
  2. De-duplicate close notifications in the client before sending them
  3. Restart the Ruff server to resynchronize session state, then reopen documents as needed
Defensive patterns

Strategy: validation

Validate before calling

// client-side: only send didClose for documents currently open
if (openDocuments.has(uri)) {
  openDocuments.delete(uri);
  sendTextDocumentDidClose(uri);
} // else skip duplicate close

Try / catch

if let Err(e) = session.close_document(&key, enc) { if e.to_string().contains("Tried to close unavailable document") { /* treat as already closed; continue */ } }

Prevention

When it happens

Trigger: Calling `session.close_document(key, encoding)` where `uri_for_key(key)` returns None — closing a document that was never opened, was already closed, or belongs to a notebook cell (cells take the early-return path only when found in `notebook_cells`).

Common situations: Duplicate `textDocument/didClose` notifications from an editor plugin; client sends didClose after a server restart lost session state; closing a notebook cell URI that was never registered.

Related errors


AI-assisted analysis of astral-sh/ruff@26f38c119c (2026-09-05). Data as JSON: /api/errors/6917c4af367e8ce0. Report an issue: GitHub.

Appendix: source

Thrown at crates/ruff_server/src/session/index.rs:327

        }
        self.documents
            .insert(notebook_uri, DocumentController::new_notebook(document));
    }

    pub(super) fn close_document(&mut self, key: &DocumentKey) -> crate::Result<()> {
        // Notebook cells URIs are removed from the index here, instead of during
        // `update_notebook_document`. This is because a notebook cell, as a text document,
        // is requested to be `closed` by VS Code after the notebook gets updated.
        // This is not documented in the LSP specification explicitly, and this assumption
        // may need revisiting in the future as we support more editors with notebook support.
        if let DocumentKey::NotebookCell(uri) = key {
            if self.notebook_cells.remove(uri).is_none() {
                tracing::warn!("Tried to remove a notebook cell that does not exist: {uri}",);
            }
            return Ok(());
        }
        let Some(uri) = self.uri_for_key(key).cloned() else {
            anyhow::bail!("Tried to close unavailable document `{key}`");
        };

        let Some(_) = self.documents.remove(&uri) else {
            anyhow::bail!("tried to close document that didn't exist at {uri}")
        };
        Ok(())
    }

    pub(super) fn client_settings(&self, key: &DocumentKey) -> Option<Arc<ClientSettings>> {
        let uri = self.uri_for_key(key)?;
        let WorkspaceSettings {
            client_settings, ..
        } = self.settings_for_uri(uri)?;
        Some(client_settings.clone())
    }

    fn document_controller_for_key(
        &mut self,

View on GitHub (pinned to 26f38c119c)