jackwener/OpenCLI · error
Plugin "${name}" is not installed.
Error message
Plugin "${name}" is not installed. What it means
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.
Source
Thrown at src/plugin.ts:1055
if (!commitHash) return;
upsertLockEntry(lock, name, {
source: { kind: 'git', url: cloneUrl },
commitHash,
installedAt: existing?.installedAt ?? new Date().toISOString(),
updatedAt: new Date().toISOString(),
});
}
/**
* Uninstall a plugin by name.
* For monorepo sub-plugins: removes symlink and cleans up the monorepo
* directory when no more sub-plugins reference it.
*/
export function uninstallPlugin(name: string): void {
const targetDir = path.join(PLUGINS_DIR, name);
if (!fs.existsSync(targetDir)) {
throw new Error(`Plugin "${name}" is not installed.`);
}
const lock = readLockFile();
const lockEntry = lock[name];
// Check if this is a symlink (monorepo sub-plugin)
const isSymlink = isSymlinkSync(targetDir);
if (isSymlink) {
// Remove symlink only (not the actual directory)
fs.unlinkSync(targetDir);
} else {
fs.rmSync(targetDir, { recursive: true, force: true });
}
// Clean up monorepo directory if no more sub-plugins reference it
if (lockEntry?.source.kind === 'monorepo') {
delete lock[name];View on GitHub (pinned to 49907e53dc)
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.
Example fix
// before
uninstallPlugin("my-plguin");
// after
const dir = path.join(PLUGINS_DIR, "my-plugin");
if (fs.existsSync(dir)) {
uninstallPlugin("my-plugin");
} Defensive patterns
Strategy: validation
Validate before calling
import fs from "fs";
import path from "path";
function canUninstall(name: string): boolean {
return fs.existsSync(path.join(PLUGINS_DIR, name));
}
if (canUninstall("my-plugin")) uninstallPlugin("my-plugin"); Try / catch
try {
uninstallPlugin(name);
} catch (e) {
if (e instanceof Error && e.message === `Plugin "${name}" is not installed.`) {
// treat as already-uninstalled; continue idempotently
} else throw e;
} Prevention
- 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.
When it happens
Trigger: 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.
Common situations: 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.
Related errors
- Twitter likes output file is missing for resume state: ${out
- Directory "${targetDir}" already exists and is not empty.
- Local plugin path is not a directory: ${localPath}
- Failed to link host opencli into plugin: ${getErrorMessage(e
- Verify command returned no metric for baseline
AI-assisted analysis of jackwener/OpenCLI@49907e53dc (2026-08-29).
Data as JSON: /api/errors/ad97a458665913ac.
Report an issue: GitHub.