{"record":{"id":"d026f5fa2861674c","repo":"santifer/career-ops","slug":"plugin-id-inactive-reason-run-node-doct","errorCode":null,"errorMessage":"plugin \"${id}\" inactive: ${reason}. Run `node doctor.mjs` for setup.","messagePattern":"plugin \"(.+?)\" inactive: (.+?)\\. Run `node doctor\\.mjs` for setup\\.","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"plugins/_engine.mjs","lineNumber":695,"sourceCode":" *     bundled zero-key provider).\n *  4. detect-EXEMPT: a merged provider's detect() is forced to null, so it fires\n *     ONLY on an explicit `provider: <id>` portals.yml entry — never via\n *     auto-detection (no surprise paid/keyed network during a plain scan).\n *  5. A known-but-inactive provider plugin registers a STUB whose fetch throws\n *     an actionable message (disabled / missing key) — so `provider: apify` with\n *     the plugin off yields a helpful error, not a confusing \"unknown provider\".\n *\n * @param {Map<string, any>} providersMap   The Map returned by scan.mjs loadProviders.\n * @param {{ root: string }} opts\n */\n// A detect-exempt provider whose fetch throws an actionable message — used when\n// a known provider plugin is inactive (disabled / missing key / failed import)\n// so an explicit `provider: <id>` portals.yml entry stays self-explaining.\nfunction inactiveProviderStub(id, reason) {\n  return {\n    id,\n    detect: () => null,\n    fetch: async () => { throw new Error(`plugin \"${id}\" inactive: ${reason}. Run \\`node doctor.mjs\\` for setup.`); },\n  };\n}\n\nexport async function mergeProviderPlugins(providersMap, { root }) {\n  if (!existsSync(pluginsConfigPath(root))) return; // (1) opted out → inert (no work, no env read)\n\n  // Everything past the opt-out gate is wrapped so an UNANTICIPATED throw\n  // (a callee regression) degrades to a ⚠️ and leaves the core providers Map\n  // untouched — fail-open is enforced structurally here, not just emergently.\n  try {\n    const cfg = await loadPluginConfig(root);\n    const providerManifests = discoverPlugins(pluginRoots(root), resolveSuccessorIds(root)).filter(m => m.hooks.includes('provider'));\n    if (providerManifests.length === 0) return;\n\n    // Only the plugins the user actually switched on in plugins.yml matter.\n    const configuredOn = providerManifests.filter(m => cfg?.plugins?.[m.id]?.enabled === true);\n    if (configuredOn.length === 0) return;\n","sourceCodeStart":677,"sourceCodeEnd":713,"githubUrl":"https://github.com/santifer/career-ops/blob/aac998c7ed7248ea853b720ceeb1fdbeb322fc5d/plugins/_engine.mjs#L677-L713","documentation":"When a portals.yml entry names provider: <id> but that provider plugin is inactive (disabled in config, missing its API key, or failed to import), the engine substitutes an inactiveProviderStub whose fetch() always throws this self-explaining error. It exists so the failure names the plugin and the remedy instead of surfacing as a generic missing-provider error.","triggerScenarios":"A scan run against a portals.yml entry with provider: <id> where the plugin is toggled off in config/plugins.yml, its required env/API key is unset, or its module throws on import.","commonSituations":"New machine missing the API key env var; plugin disabled after a config cleanup; a plugin update broke its import; running doctor was never completed on this checkout.","solutions":["Run node doctor.mjs to complete/verify setup","Enable the plugin in config/plugins.yml (set enabled: true) or via the plugins.mjs enable command","Set the plugin's required API key env variable","If the plugin fails to import, check its logs/fix or reinstall it"],"exampleFix":"// before: portals.yml has provider: greenhouse-plus, but key unset and plugin disabled\nawait provider.fetch(url);\n// Error: plugin \"greenhouse-plus\" inactive: missing API key GREENHOUSE_PLUS_KEY. Run `node doctor.mjs` for setup.\n// after\nexport GREENHOUSE_PLUS_KEY=...   # and enable it\n// node plugins.mjs enable greenhouse-plus","handlingStrategy":"try-catch","validationCode":"// Before scanning, confirm the provider plugin is active:\nimport { existsSync, readFileSync } from 'node:fs';\nconst cfg = existsSync('config/plugins.yml') ? readFileSync('config/plugins.yml', 'utf8') : '';\n// check enabled:true and the plugin's required env key is set\nif (!process.env.GREENHOUSE_PLUS_KEY) console.warn('provider key missing — run node doctor.mjs');","typeGuard":"const isInactiveProviderError = (e) => /plugin \".*\" inactive:/.test(String(e.message));","tryCatchPattern":"try { await provider.fetch(url); } catch (e) { if (isInactiveProviderError(e)) { console.error(e.message); /* run node doctor.mjs, enable the plugin, set its key */ return null; } throw e; }","preventionTips":["Run node doctor.mjs on every new machine/checkout before scanning","Keep provider API keys in .env and verify they are loaded","After plugin upgrades, confirm imports succeed with a quick smoke test"],"tags":["plugins","configuration","providers"],"backgroundTag":"feature-not-enabled","analyzedSha":"aac998c7ed7248ea853b720ceeb1fdbeb322fc5d","analyzedAt":"2026-09-16T06:35:29.214Z","contentChangedAt":"2026-09-16T06:35:29.214Z","schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}