jackwener/OpenCLI · error · PluginError
npm install failed in ${dir}: ${getErrorMessage(err)}
Error message
npm install failed in ${dir}: ${getErrorMessage(err)} What it means
installDependencies runs `npm install --omit=dev --ignore-scripts` inside the plugin (or monorepo root) directory. If npm exits non-zero, the error is wrapped in a PluginError pointing at the failing directory. opencli throws this because plugin dependencies could not be installed, so the plugin would fail to load its imports.
Source
Thrown at src/plugin.ts:581
const pkgJsonPath = path.join(dir, 'package.json');
if (!fs.existsSync(pkgJsonPath)) return;
try {
// --ignore-scripts is a security boundary, not an optimization: the plugin
// repo was just cloned from an untrusted third-party Git URL, and without
// this flag npm would execute preinstall/install/postinstall lifecycle
// scripts declared by the plugin (and every transitive dep) with the user's
// privileges at install time. Adapter plugins don't need lifecycle scripts
// to work — the adapter code is loaded later by the discovery path — so we
// deny that extra execution vector unconditionally. See issue #1753.
execFileSync('npm', ['install', '--omit=dev', '--ignore-scripts'], {
cwd: dir,
encoding: 'utf-8',
stdio: ['pipe', 'pipe', 'pipe'],
...(isWindows && { shell: true }),
});
} catch (err) {
throw new PluginError(`npm install failed in ${dir}: ${getErrorMessage(err)}`, 'Check your network connection and npm configuration.');
}
}
function finalizePluginRuntime(pluginDir: string): void {
// Symlink host opencli so TS plugins resolve '@jackwener/opencli/registry'
// against the running host, not a stale npm-published version.
linkHostOpencli(pluginDir);
// Transpile .ts → .js via esbuild (production node can't load .ts directly).
transpilePluginTs(pluginDir);
}
/**
* Shared post-install lifecycle for standalone plugins.
*/
function postInstallLifecycle(pluginDir: string): void {
installDependencies(pluginDir);
finalizePluginRuntime(pluginDir);View on GitHub (pinned to 49907e53dc)
Solutions
- Reproduce manually: `cd <dir> && npm install --omit=dev --ignore-scripts` to see the real npm error.
- Check network/registry access and .npmrc configuration (registry URL, auth tokens, proxy).
- Fix the plugin's package.json dependency versions (pin existing, published versions).
- Delete node_modules and package-lock.json in the plugin dir and retry.
- Update npm (`npm install -g npm`) if the npm error indicates an npm bug.
Example fix
// before (package.json)
"dependencies": { "undici": "^99.0.0" } // version doesn't exist
// after
"dependencies": { "undici": "^6.0.0" } Defensive patterns
Strategy: retry
Validate before calling
import { execFileSync } from 'node:child_process';
function npmReachable() {
try {
execFileSync('npm', ['ping'], { stdio: 'pipe' });
return true;
} catch { return false; }
} Try / catch
try {
installPlugin(source);
} catch (err) {
if (err instanceof PluginError && err.message.startsWith('npm install failed')) {
// inspect npm config/registry, fix deps, then retry once
} else throw err;
} Prevention
- Verify `npm ping` / registry access and .npmrc auth before installing plugins.
- Keep plugin package.json dependencies pinned to published versions.
- Run `npm install --omit=dev --ignore-scripts` in the plugin repo during CI to catch issues early.
- Update npm regularly; clear a corrupted package-lock.json when versions shift.
When it happens
Trigger: installPlugin/installLocalPlugin/updatePlugin reaching postInstallLifecycle or postInstallMonorepoLifecycle when the plugin has a package.json: npm registry unreachable, invalid package.json dependency spec, version conflicts, private registry auth missing, corrupted package-lock.json, or npm missing/broken on Windows shell.
Common situations: Corporate proxy or offline npm; plugin depends on an unpublished or renamed package; bad .npmrc (registry URL, auth token expired); ERESOLVE peer-dependency conflicts; out-of-date npm.
Related errors
- ${label} request failed: ${err?.message ?? err}
- ${label} returned malformed JSON: ${err?.message ?? err}
- Failed to clone plugin: ${getErrorMessage(err)}
- 12306 queryByTrainNo returned HTTP ${resp.status}
- 12306 queryByTrainNo returned non-JSON body
AI-assisted analysis of jackwener/OpenCLI@49907e53dc (2026-08-29).
Data as JSON: /api/errors/75dbafce6675823b.
Report an issue: GitHub.