{"record":{"id":"7d0f21f659785eb3","repo":"santifer/career-ops","slug":"plugin-rejected-result-problems-join","errorCode":null,"errorMessage":"plugin rejected:\n  - ${result.problems.join('\n  - ')}","messagePattern":"plugin rejected:\n  - (.+?)","errorType":"validation","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"plugin-install.mjs","lineNumber":89,"sourceCode":"  return { ok: true, problems: [], manifest, dir };\n}\n\n/**\n * Install a community plugin from a github repo at a pinned SHA into\n * plugins.local/<id>. Returns { id, manifest, integrity, dir } WITHOUT enabling\n * it (the caller runs the consent gate). Throws on any validation failure.\n */\nexport function installFromRepo(root, { url, sha }) {\n  const { url: safeUrl, id } = parseRepoArg(url);\n  const dest = path.join(root, 'plugins.local', id);\n  if (existsSync(dest)) throw new Error(`plugins.local/${id} already exists — \\`node plugins.mjs remove ${id}\\` first`);\n  let cloned = safeClone(safeUrl, sha);\n  let result;\n  try { result = validateInstall(cloned, id); }\n  catch (e) { rmSync(cloned, { recursive: true, force: true }); throw e; }\n  if (!result.ok) {\n    rmSync(result.dir || cloned, { recursive: true, force: true });\n    throw new Error(`plugin rejected:\\n  - ${result.problems.join('\\n  - ')}`);\n  }\n  mkdirSync(path.join(root, 'plugins.local'), { recursive: true });\n  cpSync(result.dir, dest, { recursive: true });\n  rmSync(result.dir, { recursive: true, force: true });\n  const tree = hashPluginTree(dest);\n  return { id, manifest: { ...result.manifest, dir: dest }, integrity: tree.integrity, files: tree.files, repo: safeUrl, sha };\n}\n\n/**\n * Clone + statically validate a registry entry WITHOUT installing it (used by\n * the registry-validate CI). Executes NO plugin code — manifest is parsed, the\n * audit is static. Returns problems (empty = clean).\n * @returns {string[]}\n */\nexport function auditRegistryEntry(url, sha, expectId) {\n  let parsed;\n  try { parsed = parseRepoArg(url); } catch (e) { return [e.message]; }\n  if (expectId && parsed.id !== expectId) return [`repo \"${url}\" → id \"${parsed.id}\" but registry id is \"${expectId}\"`];","sourceCodeStart":71,"sourceCodeEnd":107,"githubUrl":"https://github.com/santifer/career-ops/blob/60398d6549a46f5266929538af21cfab94badc75/plugin-install.mjs#L71-L107","documentation":"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.","triggerScenarios":"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.","commonSituations":"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).","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"],"exampleFix":"# before — repo missing LICENSE → 'plugin rejected: missing required file: LICENSE'\n# after — fix upstream, then reinstall at the new commit\ncd career-ops-plugin-foo && touch LICENSE && git add LICENSE && git commit -m 'add license' && git push\nnode plugins.mjs install acme/career-ops-plugin-foo --sha <new-full-sha>","handlingStrategy":"try-catch","validationCode":"// Pre-flight the cheap parts locally before invoking the installer\nimport fs from 'node:fs';\nfunction quickPluginCheck(dir) {\n  const problems = [];\n  for (const f of ['manifest.json', 'index.mjs', 'README.md', 'LICENSE'])\n    if (!fs.existsSync(`${dir}/${f}`)) problems.push(`missing required file: ${f}`);\n  return problems;\n}","typeGuard":null,"tryCatchPattern":"try {\n  installFromRepo(root, { url, sha });\n} catch (e) {\n  if (e.message.startsWith('plugin rejected')) {\n    console.error(e.message); // the bulleted problems list is embedded — surface it verbatim\n    process.exitCode = 1;\n  } else throw e;\n}","preventionTips":["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"],"tags":["plugin-install","validation","manifest","audit"],"backgroundTag":"plugin-validation-failed","analyzedSha":"60398d6549a46f5266929538af21cfab94badc75","analyzedAt":"2026-08-20T23:00:06.764Z","schemaVersion":2},"datasetVersion":"2026-08-21T13:17:26.733Z"}