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

  1. Pass the scope explicitly: uninstallPlugin(id, "user") or uninstallPlugin(id, "project")
  2. Decide which copy you want to keep and remove only the other scope
  3. Run uninstall twice with each scope if you want both removed
  4. 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

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


AI-assisted analysis of can1357/oh-my-pi@9690622007 (2026-08-31). Data as JSON: /api/errors/901d621532a65675. Report an issue: GitHub.