can1357/oh-my-pi · error · Error

Plugin "${pluginId}" is already installed. Use force option

Error message

Plugin "${pluginId}" is already installed. Use force option to reinstall.

What it means

installPlugin guards against silently overwriting an existing installation: it looks the plugin up in the installed-plugins registry, and if an entry already exists and force was not passed, it refuses to proceed. Passing force: true opts into reinstallation.

Source

Thrown at packages/coding-agent/src/extensibility/plugins/marketplace/manager.ts:270

		const mktEntry = getMarketplaceEntry(mktReg, marketplace);
		if (!mktEntry) {
			throw new Error(`Marketplace "${marketplace}" not found`);
		}

		// 2. Find plugin in catalog
		const catalog = await this.#readCatalog(mktEntry);
		const pluginEntry = catalog.plugins.find(p => p.name === name);
		if (!pluginEntry) {
			throw new Error(`Plugin "${name}" not found in marketplace "${marketplace}"`);
		}

		const pluginId = buildPluginId(name, marketplace);

		// 3. Check if already installed
		const instReg = await readInstalledPluginsRegistry(registryPath);
		const existing = getInstalledPlugin(instReg, pluginId);
		if (existing && existing.length > 0 && !force) {
			throw new Error(`Plugin "${pluginId}" is already installed. Use force option to reinstall.`);
		}

		// 4. Resolve source path.
		// marketplaceClonePath is the marketplace root — the directory containing .claude-plugin/
		// catalogPath is <marketplacesCacheDir>/<name>/marketplace.json, so the root is two levels up.
		// For local sources the content was fetched from a local path; the stored catalog is a copy
		// under marketplacesCacheDir. We need the original source root for resolving relative paths.
		// Use: path.dirname(catalogPath) is <cacheDir>/<name>/, and that IS the stored copy root,
		// so `path.resolve(mktEntry.catalogPath, "../..")` = parent of <name>/ inside cacheDir
		// which is wrong for local sources. Instead, derive from the stored catalog directory:
		// stored at: <marketplacesCacheDir>/<catalogName>/marketplace.json
		// The marketplace root for local sources should be the actual local path, but we only have
		// sourceUri. For local sources, use path.resolve of sourceUri; for others use the cache dir.
		const marketplaceClonePath = this.#resolveMarketplaceRoot(mktEntry);

		// URL-sourced marketplaces only cache marketplace.json, not the full plugin tree.
		// Relative string sources ("./plugins/foo") cannot be resolved against the cache dir.
		if (mktEntry.sourceType === "url" && typeof pluginEntry.source === "string") {

View on GitHub (pinned to 9690622007)

Solutions

  1. Pass the force option ({ force: true }) to reinstall over the existing copy
  2. If the plugin is fine as-is, skip the install — it is already present
  3. Uninstall the plugin first (uninstallPlugin) then reinstall cleanly
  4. Inspect the installed-plugins registry to confirm the existing entry and remove stale leftovers if needed

Example fix

// before
await manager.installPlugin("formatter", "community");
// after
await manager.installPlugin("formatter", "community", { force: true });
Defensive patterns

Strategy: try-catch

Validate before calling

const instReg = await readInstalledPluginsRegistry(registryPath);
const already = getInstalledPlugin(instReg, buildPluginId(name, marketplace));
if (already && already.length > 0 && !force) {
  // skip install or set force
} else {
  await manager.installPlugin(name, marketplace, { force });
}

Try / catch

try {
  await manager.installPlugin(name, marketplace);
} catch (err) {
  if (err instanceof Error && err.message.includes("already installed")) {
    // desired state already reached; treat as success
  } else throw err;
}

Prevention

When it happens

Trigger: Calling installPlugin(name, marketplace) without options.force when buildPluginId(name, marketplace) already has entries in the installed plugins registry; also reachable via upgradePlugin if it does not set force.

Common situations: Re-running an install command that already succeeded; a failed/partial earlier install left a stale registry entry; installing the same plugin from two scripts; wanting to overwrite local modifications to an installed plugin.

Related errors


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