jackwener/OpenCLI · error · PluginError

Local plugin path does not exist: ${localPath}

Error message

Local plugin path does not exist: ${localPath}

What it means

installLocalPlugin symlinks a local directory into the plugins dir for plugin development. It throws a PluginError when the given path does not exist on disk (an adjacent check throws for non-directories). opencli throws this early so a clearly invalid path never gets symlinked or recorded in the lock file.

Source

Thrown at src/plugin.ts:772

      upsertLockEntry(lock, pluginName, {
        source: { kind: 'git', url: cloneUrl },
        commitHash,
      });
      writeLockFile(lock);
    }
  });

  return pluginName;
}

/**
 * Install a local plugin by creating a symlink.
 * Used for plugin development: the source directory is symlinked into
 * the plugins dir so changes are reflected immediately.
 */
function installLocalPlugin(localPath: string, name: string): string {
  if (!fs.existsSync(localPath)) {
    throw new PluginError(`Local plugin path does not exist: ${localPath}`);
  }

  const stat = fs.statSync(localPath);
  if (!stat.isDirectory()) {
    throw new PluginError(`Local plugin path is not a directory: ${localPath}`);
  }

  const manifest = readPluginManifest(localPath);

  if (manifest?.opencli && !checkCompatibility(manifest.opencli)) {
    throw new PluginError(
      `Plugin requires opencli ${manifest.opencli}, but current version is incompatible.`,
      'Upgrade opencli to a compatible version.',
    );
  }

  const pluginName = manifest?.name ?? name;
  const targetDir = path.join(PLUGINS_DIR, pluginName);

View on GitHub (pinned to 49907e53dc)

Solutions

  1. Verify the path exists: `ls <localPath>`; fix typos.
  2. Pass an absolute path (relative paths are resolved against the process cwd, which may differ from your shell).
  3. Rebuild/restore the plugin directory if it was deleted, or clone it again.
  4. Ensure the path points to a directory containing the plugin, not a file or archive.
  5. Use file:///absolute/path form if passing a file URL.

Example fix

// before
installPlugin('~/dev/my-plugin');   // ~ not expanded, doesn't exist
// after
installPlugin('/home/me/dev/my-plugin');
Defensive patterns

Strategy: validation

Validate before calling

import * as fs from 'node:fs';
import * as path from 'node:path';
const localPath = path.resolve(expandHome(inputPath));
if (!fs.existsSync(localPath)) throw new Error(`Path not found: ${localPath}`);
if (!fs.statSync(localPath).isDirectory()) throw new Error(`Not a directory: ${localPath}`);

Try / catch

try {
  installPlugin(localPath);
} catch (err) {
  if (err instanceof PluginError && err.message.startsWith('Local plugin path does not exist')) {
    // correct the path (resolve to absolute, expand ~) and retry
  } else throw err;
}

Prevention

When it happens

Trigger: installPlugin('/abs/path/to/plugin') or installPlugin('file:///abs/path/to/plugin') where the path doesn't exist: typo in path, directory deleted/renamed, path valid on another machine, or passing a relative path resolved against the wrong cwd.

Common situations: Plugin development folder moved or removed; wrong absolute path after switching machines/containers; passing a file instead of a directory; shell tab-completion used a stale path; Windows vs POSIX path style confusion.

Related errors


AI-assisted analysis of jackwener/OpenCLI@49907e53dc (2026-08-29). Data as JSON: /api/errors/aa27403d89661eda. Report an issue: GitHub.