{"record":{"id":"d480811f0afbc291","repo":"santifer/career-ops","slug":"label-skipping-reason","errorCode":null,"errorMessage":"⚠️  ${label}: skipping — ${reason}","messagePattern":"⚠️  (.+?): skipping — (.+?)","errorType":"console","errorClass":null,"httpStatus":null,"severity":"info","filePath":"plugins/_engine.mjs","lineNumber":62,"sourceCode":"export const RESERVED_ENV = new Set([\n  'GEMINI_API_KEY', 'GEMINI_MODEL',\n  'OPENROUTER_API_KEY', 'CAREER_OPS_MODEL',\n  'OPENAI_API_KEY', 'OPENAI_BASE_URL', 'OPENAI_MODEL',\n  'ANTHROPIC_API_KEY',\n  'CAREER_OPS_PORTALS', 'CAREER_OPS_PROFILE',\n  'PATH', 'HOME', 'NODE_OPTIONS', 'LD_PRELOAD', 'NODE_EXTRA_CA_CERTS',\n]);\n\nconst ID_RE = /^[a-z0-9][a-z0-9-]*$/;\nconst DEFAULT_HOOK_TIMEOUT_MS = 15_000;\nconst MAX_REDIRECTS = 5;\n\nfunction isReservedEnv(name) {\n  return RESERVED_ENV.has(name) || /^AWS_/.test(name);\n}\n\nfunction warnSkip(label, reason) {\n  console.warn(`⚠️  ${label}: skipping — ${reason}`);\n}\n\nfunction isWithinDirectory(rootAbs, candidateAbs) {\n  const rel = path.relative(rootAbs, candidateAbs);\n  return rel === '' || (!rel.startsWith(`..${path.sep}`) && rel !== '..' && !path.isAbsolute(rel));\n}\n\nfunction nearestExistingPath(absPath) {\n  let current = path.resolve(absPath);\n  while (!existsSync(current)) {\n    const parent = path.dirname(current);\n    if (parent === current) return null;\n    current = parent;\n  }\n  return current;\n}\n\nfunction isSafePluginPath(rootAbs, candidateAbs) {","sourceCodeStart":44,"sourceCodeEnd":80,"githubUrl":"https://github.com/santifer/career-ops/blob/1696bec4d021768e7359f9aad6b329cba883da20/plugins/_engine.mjs#L44-L80","documentation":"warnSkip in plugins/_engine.mjs is the plugin engine's standard notice that a plugin was skipped and why. It only formats and prints the message; the actual skip decision happens at call sites (e.g. disallowed env vars detected by isReservedEnv, plugin outside the root directory per isWithinDirectory, missing config). It is informational: the plugin did not run, and the pipeline continues with the remaining plugins.","triggerScenarios":"Loading/running plugins when a plugin requires an environment variable that is reserved or blocked (RESERVED_ENV names or any AWS_* var, to prevent credential leakage into plugins), the plugin file resolves outside the allowed root directory (path traversal guard), or plugin-specific preconditions (missing config/credentials) fail.","commonSituations":"A user sets AWS_SECRET_ACCESS_KEY globally and a plugin requests it — the engine refuses and skips; a plugin is symlinked or installed outside the project root and fails the isWithinDirectory containment check; a plugin's required env var simply isn't exported in the current shell or CI job.","solutions":["Read the <reason> part of the message — it names the exact precondition that failed (blocked env var, path outside root, missing config)","If the needed value is reserved/blocked (e.g. AWS_*), provide the credential through the plugin's own dedicated variable name instead of a reserved one","If the path check skipped the plugin, install/symlink-resolve the plugin inside the project root directory","Export the required environment variable in the shell or CI environment and re-run the plugin engine"],"exampleFix":"// before: plugin wants blocked var\n// plugins.yml:  env: [AWS_SECRET_ACCESS_KEY]  → ⚠️ myplugin: skipping — reserved env AWS_SECRET_ACCESS_KEY\n// after: use a dedicated, non-reserved variable\n// plugins.yml:  env: [MYPLUGIN_AWS_KEY]\nexport MYPLUGIN_AWS_KEY=...   # then re-run the engine","handlingStrategy":"validation","validationCode":"import { existsSync } from 'fs';\nconst RESERVED = new Set(['PATH', 'HOME', 'SHELL', 'AWS_SECRET_ACCESS_KEY']);\nfunction pluginWillRun(name, env, pluginPath, root) {\n  const badEnv = env.filter(v => RESERVED.has(v) || v.startsWith('AWS_'));\n  if (badEnv.length) return `blocked env: ${badEnv.join(',')}`;\n  if (!existsSync(pluginPath)) return 'plugin file missing';\n  if (!path.resolve(pluginPath).startsWith(path.resolve(root))) return 'plugin outside root';\n  return null;\n}","typeGuard":null,"tryCatchPattern":"const results = await engine.runPlugins();\nfor (const r of results) {\n  if (r.skipped) console.warn(`${r.name} did not run: ${r.reason} — pipeline output may be incomplete`);\n}","preventionTips":["Never request reserved or AWS_* env vars in plugin manifests; use plugin-specific variable names","Keep plugins installed inside the project root so the containment check passes","Export all required plugin env vars in the shell/CI before running the engine","Parse the ⚠️ skip lines in logs and alert when an expected plugin was skipped"],"tags":["plugins","security","environment","skip"],"backgroundTag":"plugin-skipped-reserved-env","analyzedSha":"1696bec4d021768e7359f9aad6b329cba883da20","analyzedAt":"2026-09-01T19:19:23.111Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-14T05:17:10.506Z"}