jackwener/OpenCLI · error · PluginError

Sub-plugin " " is disabled in the manifest.

Error message

Sub-plugin "${subPlugin}" is disabled in the manifest.

What it means

When installing a specific sub-plugin from a monorepo, opencli filters the enabled plugins from the manifest. If the filter yields nothing, it distinguishes two cases: the sub-plugin exists in the manifest but is disabled, which produces this error. It tells you the name is correct but the manifest has it turned off.

Solutions

  1. Enable the sub-plugin in the monorepo manifest (remove `disabled: true` from its entry).
  2. If disabled via local config/override, re-enable it there and retry.
  3. If you don't need this specific sub-plugin, install a different enabled one or the whole monorepo.

Example fix

// before (monorepo manifest)
"lint": { "path": "packages/lint", "disabled": true }
// after
"lint": { "path": "packages/lint" }
Defensive patterns

Strategy: try-catch

Validate before calling

const manifest = JSON.parse(fs.readFileSync(path.join(cloneDir, 'opencli.json'), 'utf8'));
const entry = manifest.plugins?.[subPlugin];
if (entry?.disabled) console.warn(`Sub-plugin ${subPlugin} is disabled in the manifest`);

Type guard

function isSubPluginEnabled(m: { plugins?: Record<string, { disabled?: boolean }> }, name: string): boolean {
  const e = m.plugins?.[name];
  return !!e && !e.disabled;
}

Try / catch

try {
  await opencli.plugin.installFromMonorepo(repo, subPlugin);
} catch (e) {
  if (e.message.includes('is disabled in the manifest')) {
    console.error(`Enable "${subPlugin}" in the monorepo manifest or pick an enabled plugin.`);
  } else throw e;
}

Prevention

When it happens

Trigger: Running a monorepo install targeting a sub-plugin name that is present in effectiveManifest.plugins but has `disabled: true`, so getEnabledPlugins excluded it and the filter matched zero entries.

Common situations: A monorepo maintainer disabled a plugin upstream and you pull the updated manifest; you disabled the plugin locally in config earlier and forgot; copying a manifest snippet with a disabled flag.

Related errors


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

Appendix: source

Thrown at src/plugin.ts:872

  const repoDir = path.join(monoreposDir, repoName);
  const repoAlreadyInstalled = fs.existsSync(repoDir);
  const repoRoot = repoAlreadyInstalled ? repoDir : cloneDir;
  const effectiveManifest = repoAlreadyInstalled ? readPluginManifest(repoDir) : manifest;

  if (!effectiveManifest || !isMonorepo(effectiveManifest)) {
    throw new PluginError(`Monorepo manifest missing or invalid at ${repoRoot}`);
  }

  let pluginsToInstall = getEnabledPlugins(effectiveManifest);

  // If a specific sub-plugin was requested, filter to just that one
  if (subPlugin) {
    pluginsToInstall = pluginsToInstall.filter((p) => p.name === subPlugin);
    if (pluginsToInstall.length === 0) {
      // Check if it exists but is disabled
      const disabled = effectiveManifest.plugins?.[subPlugin];
      if (disabled) {
        throw new PluginError(`Sub-plugin "${subPlugin}" is disabled in the manifest.`);
      }
      throw new PluginError(
        `Sub-plugin "${subPlugin}" not found in monorepo. Available: ${Object.keys(effectiveManifest.plugins ?? {}).join(', ')}`
      );
    }
  }

  const installedNames: string[] = [];
  const lock = readLockFile();
  const eligiblePlugins: Array<{ name: string; entry: typeof pluginsToInstall[number]['entry'] }> = [];

  fs.mkdirSync(PLUGINS_DIR, { recursive: true });

  for (const { name, entry } of pluginsToInstall) {
    // Check sub-plugin level compatibility (overrides top-level)
    if (entry.opencli && !checkCompatibility(entry.opencli)) {
      log.warn(`Skipping "${name}": requires opencli ${entry.opencli}`);
      continue;

View on GitHub (pinned to 49907e53dc)