{"record":{"id":"ad97a458665913ac","repo":"jackwener/OpenCLI","slug":"plugin-name-is-not-installed","errorCode":null,"errorMessage":"Plugin \"${name}\" is not installed.","messagePattern":"Plugin \"(.+?)\" is not installed\\.","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"src/plugin.ts","lineNumber":1055,"sourceCode":"  if (!commitHash) return;\n\n  upsertLockEntry(lock, name, {\n    source: { kind: 'git', url: cloneUrl },\n    commitHash,\n    installedAt: existing?.installedAt ?? new Date().toISOString(),\n    updatedAt: new Date().toISOString(),\n  });\n}\n\n/**\n * Uninstall a plugin by name.\n * For monorepo sub-plugins: removes symlink and cleans up the monorepo\n * directory when no more sub-plugins reference it.\n */\nexport function uninstallPlugin(name: string): void {\n  const targetDir = path.join(PLUGINS_DIR, name);\n  if (!fs.existsSync(targetDir)) {\n    throw new Error(`Plugin \"${name}\" is not installed.`);\n  }\n\n  const lock = readLockFile();\n  const lockEntry = lock[name];\n\n  // Check if this is a symlink (monorepo sub-plugin)\n  const isSymlink = isSymlinkSync(targetDir);\n\n  if (isSymlink) {\n    // Remove symlink only (not the actual directory)\n    fs.unlinkSync(targetDir);\n  } else {\n    fs.rmSync(targetDir, { recursive: true, force: true });\n  }\n\n  // Clean up monorepo directory if no more sub-plugins reference it\n  if (lockEntry?.source.kind === 'monorepo') {\n    delete lock[name];","sourceCodeStart":1037,"sourceCodeEnd":1073,"githubUrl":"https://github.com/jackwener/OpenCLI/blob/49907e53dc3ade5c223ff0c4c2c2785687cec4e6/src/plugin.ts#L1037-L1073","documentation":"uninstallPlugin() throws this when the plugin directory does not exist under PLUGINS_DIR. The library treats a missing plugin directory as proof the plugin is not installed, so uninstalling it would be a no-op on nonexistent files. It is a guard against uninstalling something that was never installed or was already removed.","triggerScenarios":"Calling uninstallPlugin(name) when no directory exists at path.join(PLUGINS_DIR, name) — i.e. fs.existsSync(targetDir) is false. This happens for a never-installed plugin name, a typo in the name, or a plugin whose directory was deleted manually while a lock-file entry may still exist.","commonSituations":"Scripting bulk uninstalls where some plugins were already removed; typos in plugin names in CI scripts; cleaning up a checkout by deleting ~/.opencli plugin dirs by hand and then running uninstall; stale shell history re-running an old uninstall command.","solutions":["Check the plugin is installed first: verify the directory exists under PLUGINS_DIR (e.g. fs.existsSync(path.join(PLUGINS_DIR, name))) before calling uninstallPlugin.","List installed plugins to get the exact name/spelling and retry with the correct name.","If the directory was hand-deleted but lock entries remain, clean up the stale entry from the lock file directly instead of calling uninstallPlugin.","Wrap the call in try/catch and treat this error as 'already uninstalled' in idempotent scripts."],"exampleFix":"// before\nuninstallPlugin(\"my-plguin\");\n// after\nconst dir = path.join(PLUGINS_DIR, \"my-plugin\");\nif (fs.existsSync(dir)) {\n  uninstallPlugin(\"my-plugin\");\n}","handlingStrategy":"validation","validationCode":"import fs from \"fs\";\nimport path from \"path\";\nfunction canUninstall(name: string): boolean {\n  return fs.existsSync(path.join(PLUGINS_DIR, name));\n}\nif (canUninstall(\"my-plugin\")) uninstallPlugin(\"my-plugin\");","typeGuard":null,"tryCatchPattern":"try {\n  uninstallPlugin(name);\n} catch (e) {\n  if (e instanceof Error && e.message === `Plugin \"${name}\" is not installed.`) {\n    // treat as already-uninstalled; continue idempotently\n  } else throw e;\n}","preventionTips":["Always list installed plugins before uninstalling in scripts.","Make uninstall steps idempotent by catching this error.","Never delete plugin directories manually; use the CLI so lock state stays consistent.","Copy plugin names from the list output rather than typing them."],"tags":["plugin","uninstall","filesystem","state"],"backgroundTag":"plugin-not-installed","analyzedSha":"49907e53dc3ade5c223ff0c4c2c2785687cec4e6","analyzedAt":"2026-08-29T08:14:47.543Z","schemaVersion":2},"datasetVersion":"2026-08-29T12:17:43.993Z"}