{"record":{"id":"d73551cb69c2f942","repo":"can1357/oh-my-pi","slug":"plugin-pluginid-is-already-installed-use-for","errorCode":null,"errorMessage":"Plugin \"${pluginId}\" is already installed. Use force option to reinstall.","messagePattern":"Plugin \"(.+?)\" is already installed\\. Use force option to reinstall\\.","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"packages/coding-agent/src/extensibility/plugins/marketplace/manager.ts","lineNumber":270,"sourceCode":"\t\tconst mktEntry = getMarketplaceEntry(mktReg, marketplace);\n\t\tif (!mktEntry) {\n\t\t\tthrow new Error(`Marketplace \"${marketplace}\" not found`);\n\t\t}\n\n\t\t// 2. Find plugin in catalog\n\t\tconst catalog = await this.#readCatalog(mktEntry);\n\t\tconst pluginEntry = catalog.plugins.find(p => p.name === name);\n\t\tif (!pluginEntry) {\n\t\t\tthrow new Error(`Plugin \"${name}\" not found in marketplace \"${marketplace}\"`);\n\t\t}\n\n\t\tconst pluginId = buildPluginId(name, marketplace);\n\n\t\t// 3. Check if already installed\n\t\tconst instReg = await readInstalledPluginsRegistry(registryPath);\n\t\tconst existing = getInstalledPlugin(instReg, pluginId);\n\t\tif (existing && existing.length > 0 && !force) {\n\t\t\tthrow new Error(`Plugin \"${pluginId}\" is already installed. Use force option to reinstall.`);\n\t\t}\n\n\t\t// 4. Resolve source path.\n\t\t// marketplaceClonePath is the marketplace root — the directory containing .claude-plugin/\n\t\t// catalogPath is <marketplacesCacheDir>/<name>/marketplace.json, so the root is two levels up.\n\t\t// For local sources the content was fetched from a local path; the stored catalog is a copy\n\t\t// under marketplacesCacheDir. We need the original source root for resolving relative paths.\n\t\t// Use: path.dirname(catalogPath) is <cacheDir>/<name>/, and that IS the stored copy root,\n\t\t// so `path.resolve(mktEntry.catalogPath, \"../..\")` = parent of <name>/ inside cacheDir\n\t\t// which is wrong for local sources. Instead, derive from the stored catalog directory:\n\t\t// stored at: <marketplacesCacheDir>/<catalogName>/marketplace.json\n\t\t// The marketplace root for local sources should be the actual local path, but we only have\n\t\t// sourceUri. For local sources, use path.resolve of sourceUri; for others use the cache dir.\n\t\tconst marketplaceClonePath = this.#resolveMarketplaceRoot(mktEntry);\n\n\t\t// URL-sourced marketplaces only cache marketplace.json, not the full plugin tree.\n\t\t// Relative string sources (\"./plugins/foo\") cannot be resolved against the cache dir.\n\t\tif (mktEntry.sourceType === \"url\" && typeof pluginEntry.source === \"string\") {","sourceCodeStart":252,"sourceCodeEnd":288,"githubUrl":"https://github.com/can1357/oh-my-pi/blob/969062200754ea02cfac922e5ebb8c608c079e15/packages/coding-agent/src/extensibility/plugins/marketplace/manager.ts#L252-L288","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","solutions":["Pass the force option ({ force: true }) to reinstall over the existing copy","If the plugin is fine as-is, skip the install — it is already present","Uninstall the plugin first (uninstallPlugin) then reinstall cleanly","Inspect the installed-plugins registry to confirm the existing entry and remove stale leftovers if needed"],"exampleFix":"// before\nawait manager.installPlugin(\"formatter\", \"community\");\n// after\nawait manager.installPlugin(\"formatter\", \"community\", { force: true });","handlingStrategy":"try-catch","validationCode":"const instReg = await readInstalledPluginsRegistry(registryPath);\nconst already = getInstalledPlugin(instReg, buildPluginId(name, marketplace));\nif (already && already.length > 0 && !force) {\n  // skip install or set force\n} else {\n  await manager.installPlugin(name, marketplace, { force });\n}","typeGuard":null,"tryCatchPattern":"try {\n  await manager.installPlugin(name, marketplace);\n} catch (err) {\n  if (err instanceof Error && err.message.includes(\"already installed\")) {\n    // desired state already reached; treat as success\n  } else throw err;\n}","preventionTips":["Make install scripts idempotent: catch \"already installed\" and treat as success","Use explicit force: true when you intentionally want to overwrite an install","Clean up stale registry entries after failed/aborted installs","Track installed plugin ids in one place to avoid duplicate install attempts"],"tags":["plugin-marketplace","duplicate-install","idempotency"],"backgroundTag":"plugin-already-installed","analyzedSha":"969062200754ea02cfac922e5ebb8c608c079e15","analyzedAt":"2026-08-31T10:29:35.737Z","schemaVersion":2},"datasetVersion":"2026-08-31T14:17:45.589Z"}