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
- Read the bulleted problems — each names the exact missing file, manifest issue, or audit finding
- Fix the upstream repo (PR to the plugin maintainer) or choose a different plugin
- Re-install at the corrected commit SHA once the repo validates
- 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
- Ship manifest.json, index.mjs, README.md and LICENSE in every plugin repo from day one
- Keep the manifest id exactly equal to the repo-name suffix
- Run the registry-validate/pre-check on your plugin repo in CI so rejection never surprises users
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
- each entry needs a string "input" and "output"
- invalid reportNum "${entryReport}" (use the numeric report n
- repo must be named "career-ops-plugin-<name>" (got "${repoNa
- Unsupported profile photo data URL (expected base64 PNG, JPE
- Unsupported profile photo URL scheme: ${photo.split(':', 1)[
AI-assisted analysis of santifer/career-ops@60398d6549 (2026-08-20).
Data as JSON: /api/errors/7d0f21f659785eb3.
Report an issue: GitHub.