santifer/career-ops · error · Error

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

Error message

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

What it means

Thrown by installFromRepo() when validateInstall() returns ok:false, collecting every static problem (missing required files manifest.json/index.mjs/README.md/LICENSE, invalid manifest JSON, manifest id mismatch, or audit findings). The cloned tree and its temp dir are cleaned up, and the problems are listed in one combined error.

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 9b17a8ac97)

Solutions

  1. Read every line under 'plugin rejected:' — each names a concrete defect.
  2. Fix the defects in the plugin repo at the pinned SHA (or pick a corrected SHA).
  3. Ensure manifest.json/index.mjs/README.md/LICENSE all exist and the manifest id matches the repo name suffix.
  4. Re-run `node plugins.mjs install <repo> --sha <new-sha>` after fixes.

Example fix

// before: repo missing LICENSE -> ['missing required file: LICENSE']
// after: add LICENSE at the plugin repo, pin a new SHA, reinstall
Defensive patterns

Strategy: validation

Validate before calling

// pre-flight the plugin tree before attempting install
const REQUIRED = ['manifest.json', 'index.mjs', 'README.md', 'LICENSE'];
function hasMinFiles(treeDir) {
  return REQUIRED.every(f => existsSync(path.join(treeDir, f)));
}

Type guard

null

Try / catch

try {
  installFromRepo(root, { url, sha });
} catch (e) {
  if (/plugin rejected/.test(e.message)) {
    // parse the bullet list, surface each defect to the user
  } else throw e;
}

Prevention

When it happens

Trigger: The cloned plugin is missing one of the four MIN_FILES; manifest.json is not valid JSON; the manifest's id does not match the repo-name id; plugin-audit.mjs flagged a static issue (e.g. disallowed API surface, suspicious code).

Common situations: Installing an incomplete/WIP plugin repo; manifest id drift between repo name and manifest field; the plugin author omitted LICENSE or README; the audit caught a risky pattern.

Related errors


AI-assisted analysis of santifer/career-ops@9b17a8ac97 (2026-08-13). Data as JSON: /api/errors/fabb8308c80f1ac8. Report an issue: GitHub.