{"record":{"id":"d75fb98e3235e26e","repo":"astral-sh/ruff","slug":"workspace-uri-is-not-a-file-path-uri","errorCode":null,"errorMessage":"Workspace URI is not a file path: {uri}","messagePattern":"Workspace URI is not a file path: (.+?)","errorType":"error_code","errorClass":"anyhow::Error","httpStatus":null,"severity":"error","filePath":"crates/ty_server/src/session.rs","lineNumber":994,"sourceCode":"    /// Removes a workspace folder at the given URI.\n    ///\n    /// This removes the workspace folder and its associated project database,\n    /// and clears diagnostics for any documents that were in the workspace.\n    ///\n    /// # Errors\n    ///\n    /// This returns an error if the workspace folder has already been removed\n    /// or otherwise could not be found.\n    pub(crate) fn remove_workspace_folder(\n        &mut self,\n        client: &Client,\n        uri: &Uri,\n    ) -> anyhow::Result<()> {\n        tracing::info!(\"Removing workspace folder: {uri}\");\n\n        let path = uri\n            .to_file_path()\n            .map_err(|()| anyhow!(\"Workspace URI is not a file path: {uri}\"))?;\n        let workspace_path = SystemPathBuf::from_path_buf(path)\n            .map_err(|path| anyhow!(\"Workspace path is not valid UTF-8: {}\", path.display()))?;\n\n        anyhow::ensure!(\n            self.workspaces.unregister(&workspace_path),\n            \"Workspace not found: {uri}\",\n        );\n\n        // Note that it is somewhat unclear whether we actually need to\n        // clear diagnostics here. It seems that, at least in the case\n        // of VS Code, it will auto-clear any diagnostics not found in\n        // the workspace diagnostic response. Moreover, VS Code will\n        // re-request workspace diagnostics after removing a workspace\n        // folder.\n        //\n        // For now, we keep unconditionally clearing diagnostics on\n        // opened text documents for reasons of good sense, but it's\n        // possible that we don't even need to do that (when workspace","sourceCodeStart":976,"sourceCodeEnd":1012,"githubUrl":"https://github.com/astral-sh/ruff/blob/5bb508f59481c1a02d5c7ff8568b9ffd8a763eef/crates/ty_server/src/session.rs#L976-L1012","documentation":"`Session::remove_workspace_folder` handles the `workspace/didChangeWorkspaceFolders` removal notification. It converts the workspace folder URI to a filesystem path via `Uri::to_file_path()`, and fails with this error when the URI does not denote a file-system path (e.g. a remote/virtual scheme). ty only supports local file-path workspaces, so non-`file://` URIs cannot be registered or removed as workspace folders.","triggerScenarios":"The client sends `workspace/didChangeWorkspaceFolders` removing a folder whose URI is not convertible to a path - e.g. `untitled:`, `remote://`, `vscode-remote://`, `zip:` or any virtual document scheme.","commonSituations":"Using ty over a remote-SSH / container / web extension where workspace folders use `vscode-remote://` URIs; clients that open untitled (unsaved) workspace roots; forwarding the LSP through a bridge that rewrites URIs to non-file schemes.","solutions":["Ensure the workspace root ty is opened with is backed by a real local `file://` path","If working remotely, run the ty server on the remote machine (remote extension) so URIs resolve locally there","Skip sending workspace-folder removal events for virtual/untitled folders","Convert or map virtual URIs to local file paths before forwarding the notification to ty"],"exampleFix":"// before: sending a remote workspace removal\n{\"event\":{\"removed\":[{\"uri\":\"vscode-remote://ssh-remote%2Bhost/home/project\"}]}}\n// after: use the local path as seen by the server\n{\"event\":{\"removed\":[{\"uri\":\"file:///home/user/project\"}]}}","handlingStrategy":"validation","validationCode":"// Only forward removals for file-scheme workspace folders\nfunction isFileUri(uri) {\n  try {\n    const u = new URL(uri);\n    return u.protocol === 'file:' && u.pathname !== '' && u.pathname !== '/';\n  } catch (e) { return false; }\n}\nremovedFolders.filter(f => isFileUri(f.uri)).forEach(sendRemovalToTy);","typeGuard":"function isFilePathWorkspaceFolder(f) {\n  return typeof f === 'object' && f !== null\n    && typeof f.uri === 'string'\n    && f.uri.startsWith('file://')\n    && f.uri.length > 'file://'.length;\n}","tryCatchPattern":"// Catch and report non-file workspace URIs before they reach ty\ntry {\n  tyClient.removeWorkspaceFolder(folder);\n} catch (err) {\n  if (/not a file path/i.test(err.message)) {\n    console.warn('Skipping non-file workspace folder: ' + folder.uri);\n  } else { throw err; }\n}","preventionTips":["Open ty workspaces only on local `file://` directories","For remote/container development, run the ty server inside the remote environment","Filter out untitled/virtual folders from workspace-folder change events","Map bridged URI schemes back to local paths at the client adapter layer"],"tags":["lsp","workspace","uri","file-path"],"backgroundTag":"unsupported-uri-scheme","analyzedSha":"5bb508f59481c1a02d5c7ff8568b9ffd8a763eef","analyzedAt":"2026-09-05T10:32:37.492Z","contentChangedAt":"2026-09-05T10:32:37.492Z","schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}