astral-sh/ruff · error

Tried to remove non-existent workspace URI

Error message

Tried to remove non-existent workspace URI {workspace_uri}

What it means

After converting the workspace URI to a path, Ruff removes the folder's settings with `settings.remove(&workspace_path)`. If no settings entry exists for that path, the workspace was never registered (or was already removed), so closing it fails with this error.

Solutions

  1. Make the client idempotent: don't send remove events for folders it didn't successfully add.
  2. Check server logs at startup for 'Ruff does not support non-file workspaces' — ignored folders can't be closed later.
  3. Restart the language server so workspace registration state resynchronizes with the client.
  4. Upgrade Ruff; workspace folder handling has been made more tolerant in newer releases.
Defensive patterns

Strategy: validation

Validate before calling

// client: track folders acknowledged as added before ever removing them
const addedFolders = new Set();
if (removedFolders.some(f => !addedFolders.has(f.uri))) {
  console.warn('folder was never registered with Ruff; skipping remove', f.uri);
}

Type guard

function wasRegistered(uri, addedSet) { return typeof uri === 'string' && addedSet.has(uri); }

Try / catch

try {
  await connection.notify('workspace/didChangeWorkspaceFolders', { event: { removed } });
} catch (e) {
  if (String(e.message).includes('non-existent workspace URI')) {
    addedFolders.delete(removed.uri); // resync local state
  } else { throw e; }
}

Prevention

When it happens

Trigger: A didChangeWorkspaceFolders removal for a folder that was never added, was previously ignored as a non-file workspace, or was removed twice (duplicate remove events from the client).

Common situations: Clients firing duplicate folder-remove notifications, folders skipped at startup due to non-file URIs then later 'closed', or a server that restarted losing its registry while the client still tracks folders.

Related errors


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

Appendix: source

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

    pub(super) fn open_workspace_folder(
        &mut self,
        uri: Uri,
        global: &GlobalClientSettings,
        client: &Client,
    ) -> crate::Result<()> {
        // TODO(jane): Find a way for workspace client settings to be added or changed dynamically.
        self.settings
            .register_workspace(&Workspace::new(uri), global, client)
    }

    pub(super) fn close_workspace_folder(&mut self, workspace_uri: &Uri) -> crate::Result<()> {
        let workspace_path = workspace_uri.to_file_path().map_err(|()| {
            anyhow!("Failed to convert workspace URI to file path: {workspace_uri}")
        })?;

        self.settings
            .remove(&workspace_path)
            .ok_or_else(|| anyhow!("Tried to remove non-existent workspace URI {workspace_uri}"))?;

        // O(n) complexity, which isn't ideal... but this is an uncommon operation.
        self.documents
            .retain(|uri, _| !Path::new(uri.path()).starts_with(&workspace_path));
        self.notebook_cells
            .retain(|_, uri| !Path::new(uri.path()).starts_with(&workspace_path));

        Ok(())
    }

    pub(super) fn make_document_ref(
        &self,
        key: DocumentKey,
        global: &GlobalClientSettings,
    ) -> Option<DocumentQuery> {
        let uri = self.uri_for_key(&key)?.clone();

        let document_settings = self

View on GitHub (pinned to 26f38c119c)