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 upgrade. What it means
When a plugin with the same name@marketplace id is installed in BOTH the user-scope and project-scope registries, upgradePlugin() cannot decide which copy to re-install, so it throws unless the caller explicitly passes scope. The error text mirrors the CLI's --scope flag, telling you how to disambiguate.
Source
Thrown at packages/coding-agent/src/extensibility/plugins/marketplace/manager.ts:698
async upgradePlugin(pluginId: string, scope?: "user" | "project"): Promise<InstalledPluginEntry> {
const parsed = parsePluginId(pluginId);
if (!parsed) {
throw new Error(`Invalid plugin ID: "${pluginId}". Expected "name@marketplace".`);
}
const { userEntries, projectEntries } = 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`);
}
let resolvedScope: "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 upgrade.`,
);
}
resolvedScope = scope;
} else if (inProject) {
if (scope === "user") throw new Error(`Plugin "${pluginId}" is not installed in user scope`);
resolvedScope = "project";
} else {
if (scope === "project") throw new Error(`Plugin "${pluginId}" is not installed in project scope`);
resolvedScope = "user";
}
return this.installPlugin(parsed.name, parsed.marketplace, { force: true, scope: resolvedScope });
}
// Upgrade a plugin across all scopes where it is installed.
// Returns one entry per scope upgraded (0–2 entries).
async upgradePluginAcrossScopes(pluginId: string): Promise<InstalledPluginEntry[]> {View on GitHub (pinned to 9690622007)
Solutions
- Pass the desired scope: upgradePlugin(id, "user") or upgradePlugin(id, "project").
- Decide which copy you actually want to upgrade — usually the project one for repo-pinned plugins.
- If only one copy should exist, uninstall the redundant scope entry, then upgrade the remaining one.
- In CLI usage, add --scope user or --scope project to the upgrade command.
Example fix
// before
await manager.upgradePlugin("my-plugin@official"); // installed in both scopes
// after
await manager.upgradePlugin("my-plugin@official", "project"); Defensive patterns
Strategy: validation
Validate before calling
const { userEntries, projectEntries } = /* look up both scopes */;
const inUser = !!userEntries?.length, inProject = !!projectEntries?.length;
const scopeArg = inUser && inProject ? (scope ?? "project") : scope; // disambiguate up front
await manager.upgradePlugin(pluginId, scopeArg); Type guard
null
Try / catch
try {
await manager.upgradePlugin(pluginId, scope);
} catch (err) {
if (err instanceof Error && err.message.includes("installed in both user and project scope")) {
await manager.upgradePlugin(pluginId, "project"); // pick a deterministic default
} else throw err;
} Prevention
- Always pass an explicit scope when both scopes may hold the same plugin.
- Avoid installing the same plugin in both user and project registries; pick one owner scope.
- In CLI wrappers, surface the --scope flag instead of hard-coding absence.
- Detect dual installation before upgrading and prune the redundant entry.
When it happens
Trigger: Calling upgradePlugin(id) with no second argument while an entry exists in both userEntries and projectEntries (both arrays non-empty). This is exactly the ambiguity case: inUser && inProject && !scope.
Common situations: A plugin was installed globally for personal use and later also added to a project's plugin registry; both copies linger after a scope migration; scripts call the API without the scope parameter that a human would pass as --scope on the CLI.
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/660c8ee06d4f70ba.
Report an issue: GitHub.