can1357/oh-my-pi · error · Error
Plugin "${pluginId}" is installed in both user and project s
Error message
Plugin "${pluginId}" is installed in both user and project scope. Use --scope user or --scope project to specify which to remove. What it means
When a plugin id exists in both the user-scope and project-scope registries, uninstallPlugin refuses to guess which copy to remove and demands an explicit scope argument. This protects users from deleting the wrong installation when the same plugin is installed at both levels.
Source
Thrown at packages/coding-agent/src/extensibility/plugins/marketplace/manager.ts:464
async uninstallPlugin(pluginId: string, scope?: "user" | "project", options?: { dryRun?: boolean }): Promise<void> {
const parsed = parsePluginId(pluginId);
if (!parsed) {
throw new Error(`Invalid plugin ID format: "${pluginId}". Expected "name@marketplace".`);
}
const { userEntries, projectEntries, userReg, projectReg } = await this.#findInBothRegistries(pluginId);
const inUser = userEntries && userEntries.length > 0;
const inProject = projectEntries && projectEntries.length > 0;
if (!inUser && !inProject) {
throw new Error(`Plugin "${pluginId}" is not installed`);
}
// Disambiguation: if installed in both scopes and no explicit scope, require one.
let targetScope: "user" | "project";
if (inUser && inProject) {
if (!scope) {
throw new Error(
`Plugin "${pluginId}" is installed in both user and project scope. Use --scope user or --scope project to specify which to remove.`,
);
}
targetScope = scope;
} else if (inProject) {
if (scope === "user") {
throw new Error(`Plugin "${pluginId}" is not installed in user scope`);
}
targetScope = "project";
} else {
if (scope === "project") {
throw new Error(`Plugin "${pluginId}" is not installed in project scope`);
}
targetScope = "user";
}
const targetEntries = targetScope === "project" ? projectEntries! : userEntries!;
const targetReg = targetScope === "project" ? projectReg : userReg;View on GitHub (pinned to 9690622007)
Solutions
- Pass the scope explicitly: uninstallPlugin(id, "user") or uninstallPlugin(id, "project")
- Decide which copy you want to keep and remove only the other scope
- Run uninstall twice with each scope if you want both removed
- Check both registries first to confirm what each scope holds
Example fix
// before
await manager.uninstallPlugin("formatter@community");
// after
await manager.uninstallPlugin("formatter@community", "project"); // or "user" Defensive patterns
Strategy: try-catch
Validate before calling
const userCopy = await manager.listInstalledPlugins("user");
const projCopy = await manager.listInstalledPlugins("project");
const inBoth = userCopy.some(p => p.id === pluginId) && projCopy.some(p => p.id === pluginId);
await manager.uninstallPlugin(pluginId, inBoth ? desiredScope : undefined); Try / catch
try {
await manager.uninstallPlugin(pluginId, scope); // scope may be undefined
} catch (err) {
if (err instanceof Error && err.message.includes("installed in both user and project scope")) {
await manager.uninstallPlugin(pluginId, await askUserToPickScope());
} else throw err;
} Prevention
- Always pass an explicit scope in scripts so behavior never depends on registry state
- Avoid installing the same plugin at both scopes; pick one convention per team
- Check both registries before uninstall to know whether disambiguation is needed
- In interactive CLIs, prompt for scope on this error instead of failing
When it happens
Trigger: uninstallPlugin(pluginId) with no scope argument (or scope: undefined) while the id resolves to entries in both the user registry and the project registry.
Common situations: Installing a plugin globally early on and later also project-locally for pinning; scripts written before the plugin appeared in both scopes; interactive use where the user did not know about dual installation.
Related errors
- Plugin "${pluginId}" is installed in both user and project s
- Found ${occurrences} occurrences${pathSuffix}${moreMsg}:\n\n
- Found ${result.matchCount} matches for context '${displayCon
- Found ${searchResult.matchCount} matches for the text in ${p
- Operation ${operationNumber} is ambiguous: ${candidates.leng
AI-assisted analysis of can1357/oh-my-pi@9690622007 (2026-08-31).
Data as JSON: /api/errors/901d621532a65675.
Report an issue: GitHub.