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

  1. Reproduce manually: `cd <dir> && npm install --omit=dev --ignore-scripts` to see the real npm error.
  2. Check network/registry access and .npmrc configuration (registry URL, auth tokens, proxy).
  3. Fix the plugin's package.json dependency versions (pin existing, published versions).
  4. Delete node_modules and package-lock.json in the plugin dir and retry.
  5. 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

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


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