santifer/career-ops · error · Error

plugin rejected: - ${result.problems.join(' - ')}

Error message

plugin rejected:
  - ${result.problems.join('
  - ')}

What it means

installFromRepo() throws 'plugin rejected' when post-clone validation fails: a missing required file (manifest.json, index.mjs, README.md, LICENSE), manifest.json being invalid JSON, the manifest id not matching the repo-derived id, or static security-audit findings from plugin-audit.mjs. Every problem is collected (not fail-fast) and joined into the bulleted list embedded in the message. The cloned temp dir is deleted, so nothing is installed.

Source

Thrown at plugin-install.mjs:89

  return { ok: true, problems: [], manifest, dir };
}

/**
 * Install a community plugin from a github repo at a pinned SHA into
 * plugins.local/<id>. Returns { id, manifest, integrity, dir } WITHOUT enabling
 * it (the caller runs the consent gate). Throws on any validation failure.
 */
export function installFromRepo(root, { url, sha }) {
  const { url: safeUrl, id } = parseRepoArg(url);
  const dest = path.join(root, 'plugins.local', id);
  if (existsSync(dest)) throw new Error(`plugins.local/${id} already exists — \`node plugins.mjs remove ${id}\` first`);
  let cloned = safeClone(safeUrl, sha);
  let result;
  try { result = validateInstall(cloned, id); }
  catch (e) { rmSync(cloned, { recursive: true, force: true }); throw e; }
  if (!result.ok) {
    rmSync(result.dir || cloned, { recursive: true, force: true });
    throw new Error(`plugin rejected:\n  - ${result.problems.join('\n  - ')}`);
  }
  mkdirSync(path.join(root, 'plugins.local'), { recursive: true });
  cpSync(result.dir, dest, { recursive: true });
  rmSync(result.dir, { recursive: true, force: true });
  const tree = hashPluginTree(dest);
  return { id, manifest: { ...result.manifest, dir: dest }, integrity: tree.integrity, files: tree.files, repo: safeUrl, sha };
}

/**
 * Clone + statically validate a registry entry WITHOUT installing it (used by
 * the registry-validate CI). Executes NO plugin code — manifest is parsed, the
 * audit is static. Returns problems (empty = clean).
 * @returns {string[]}
 */
export function auditRegistryEntry(url, sha, expectId) {
  let parsed;
  try { parsed = parseRepoArg(url); } catch (e) { return [e.message]; }
  if (expectId && parsed.id !== expectId) return [`repo "${url}" → id "${parsed.id}" but registry id is "${expectId}"`];

View on GitHub (pinned to 60398d6549)

Solutions

  1. Read the bulleted problems — each names the exact missing file, manifest issue, or audit finding
  2. Fix the upstream repo (PR to the plugin maintainer) or choose a different plugin
  3. Re-install at the corrected commit SHA once the repo validates
  4. If the problem list contains audit findings, do not vendor the code manually either — report the plugin

Example fix

# before — repo missing LICENSE → 'plugin rejected: missing required file: LICENSE'
# after — fix upstream, then reinstall at the new commit
cd career-ops-plugin-foo && touch LICENSE && git add LICENSE && git commit -m 'add license' && git push
node plugins.mjs install acme/career-ops-plugin-foo --sha <new-full-sha>
Defensive patterns

Strategy: try-catch

Validate before calling

// Pre-flight the cheap parts locally before invoking the installer
import fs from 'node:fs';
function quickPluginCheck(dir) {
  const problems = [];
  for (const f of ['manifest.json', 'index.mjs', 'README.md', 'LICENSE'])
    if (!fs.existsSync(`${dir}/${f}`)) problems.push(`missing required file: ${f}`);
  return problems;
}

Try / catch

try {
  installFromRepo(root, { url, sha });
} catch (e) {
  if (e.message.startsWith('plugin rejected')) {
    console.error(e.message); // the bulleted problems list is embedded — surface it verbatim
    process.exitCode = 1;
  } else throw e;
}

Prevention

When it happens

Trigger: The cloned repo lacks LICENSE; manifest.json has a syntax error or its `id` differs from the repo name suffix; the static audit flags a disallowed pattern in the plugin's shipped files.

Common situations: A plugin author forgot a required file or restructured the repo; a sloppily maintained community plugin; audit findings on a suspicious plugin (treat those seriously — do not bypass).

Related errors


AI-assisted analysis of santifer/career-ops@60398d6549 (2026-08-20). Data as JSON: /api/errors/7d0f21f659785eb3. Report an issue: GitHub.