Meta's Pyrefly sabotages competing Python extensions without telling you
Source: Hacker News
Affected extensions
detachhead.basedpyrightcodeium.windsurfpyrightanysphere.cursorpyright
Source
lsp/src/extension-interop.ts:
export async function disableWindsurfPyrightIfInstalled() {
const windsurfPyrightExtension = vscode.extensions.getExtension('codeium.windsurfpyright');
if (windsurfPyrightExtension) {
const config = vscode.workspace.getConfiguration('windsurfPyright');
await config.update('disableLanguageServices', true, vscode.ConfigurationTarget.Global);
}
}
export async function disableBasedPyrightIfInstalled() {
const basedPyrightExtension = vscode.extensions.getExtension('detachhead.basedpyright');
if (basedPyrightExtension) {
const config = vscode.workspace.getConfiguration('basedpyright');
await config.update('disableLanguageServices', true, vscode.ConfigurationTarget.Global);
}
}
export async function disableCursorPyrightIfInstalled() {
const cursorPyrightExtension = vscode.extensions.getExtension('anysphere.cursorpyright');
if (cursorPyrightExtension) {
const config = vscode.workspace.getConfiguration('cursorpyright');
await config.update('disableLanguageServices', true, vscode.ConfigurationTarget.Global);
}
}
These functions are called unconditionally in lsp/src/extension.ts on activation.
Relevant commits: 69985d1d, c0ab0d76, 72458900, 2fb5205a (December 8‑9 2025).
Why this is a problem
-
Silent global modification
ConfigurationTarget.Globalwrites to the user’s globalsettings.json, affecting every workspace. No notification or prompt is shown. -
No cleanup on deactivation or uninstall
There is nodeactivate()logic that restores the modified settings. After uninstalling Pyrefly, the targeted extensions remain broken. -
Hard‑coded extension IDs
The code disables specific competitor extensions by publisher ID rather than providing a generic conflict‑resolution mechanism. Users cannot opt out.
Live reproduction
Test 1 — VSCodium
- Before installing Pyrefly (
~/.config/VSCodium/User/settings.json):
{
"python.languageServer": "Default"
}
- After installing Pyrefly and opening a Python file:
{
"python.languageServer": "Default",
"basedpyright.disableLanguageServices": true
}
- After uninstalling Pyrefly (key persists):
{
"python.languageServer": "Default",
"basedpyright.disableLanguageServices": true
}
Test 2 — VS Code 1.118.1 (clean isolated profile)
-
Before installing Pyrefly:
settings.jsondoes not exist (empty profile). -
After installing
detachhead.basedpyright, thenmeta.pyrefly, and opening a Python file:
{
"python.languageServer": "None",
"basedpyright.disableLanguageServices": true
}
The write occurs on the first Python file open, regardless of prior configuration.
Expected behavior
If Pyrefly requires exclusive access to Python language services, it should:
- Prompt the user before modifying settings owned by other extensions.
- Restore those settings in
deactivate()when Pyrefly is disabled or uninstalled. - At minimum, log the changes so users can reverse them.
Additional concern: forced Microsoft extension dependencies
package.json declares "extensionDependencies": ["ms-python.python"]. This creates a hard bidirectional lock:
-
Installing Pyrefly automatically installs:
ms-python.pythonms-python.debugpyms-python.vscode-python-envs
-
Uninstalling any of those three also uninstalls Pyrefly.
The only functionality ms-python.python provides is querying the active Python interpreter for optional CodeLens buttons. Core LSP features (type checking, completions, hover, go‑to‑definition) run entirely through the pyrefly binary and have no dependency on ms-python.python. For users of VSCodium or other FOSS VS Code distributions who avoid Microsoft extensions, this hidden dependency is a significant side effect.
Steps to reproduce
- Install
detachhead.basedpyrightand verify it works (completions, hover, etc.). - Install
meta.pyrefly. - Open a Python file (triggers Pyrefly activation).
- Check the global
settings.json;basedpyright.disableLanguageServiceswill betrue. - Uninstall
meta.pyrefly. - Observe that
basedpyright.disableLanguageServicesremainstrue, leaving basedpyright broken with no indication why.