{"record":{"id":"e82fe61b1ae6b6e3","repo":"santifer/career-ops","slug":"local-parser-interpreter-command-requires-an-in-r","errorCode":null,"errorMessage":"local-parser: interpreter command requires an in-repo parser script","messagePattern":"local-parser: interpreter command requires an in-repo parser script","errorType":"validation","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"providers/local-parser.mjs","lineNumber":111,"sourceCode":"  const value = String(command || '');\n  if (!value) throw new Error('local-parser: parser.command is required');\n  if (!value.includes('/') && ALLOWED_INTERPRETERS.has(value)) return value;\n  return resolveInsideRoot(value);\n}\n\n// Validate the whole invocation and return what to spawn. Throws on anything unsafe.\nfunction resolveInvocation(entry) {\n  const rawCommand = String(entry.parser?.command || '');\n  const command = resolveCommand(rawCommand);\n  const args = buildParserArgs(entry);\n  const scriptPath = getParserScriptPath(entry);\n\n  const usesInterpreter = !rawCommand.includes('/') && ALLOWED_INTERPRETERS.has(rawCommand);\n  if (usesInterpreter) {\n    // A whitelisted interpreter must run an in-repo script as its FIRST argument.\n    // Anything before the script is an interpreter option (node --eval / --require,\n    // python -c, …) that could execute arbitrary code, so require the script to lead.\n    if (!scriptPath) throw new Error('local-parser: interpreter command requires an in-repo parser script');\n    resolveInsideRoot(scriptPath);\n    if (args[0] !== scriptPath) {\n      throw new Error('local-parser: the parser script must be the interpreter\\'s first argument');\n    }\n  } else if (scriptPath) {\n    // command is an in-repo file; keep any detected script path inside the repo too.\n    resolveInsideRoot(scriptPath);\n  }\n\n  return { command, args };\n}\n\nfunction normalizeJobUrl(rawUrl, baseUrl) {\n  if (!rawUrl) return '';\n  try {\n    return new URL(String(rawUrl).trim(), baseUrl || undefined).href;\n  } catch {\n    return '';","sourceCodeStart":93,"sourceCodeEnd":129,"githubUrl":"https://github.com/santifer/career-ops/blob/aac998c7ed7248ea853b720ceeb1fdbeb322fc5d/providers/local-parser.mjs#L93-L129","documentation":"When parser.command is a whitelisted interpreter (no slash, in ALLOWED_INTERPRETERS), the invocation must run an in-repo parser script as its first argument. This blocks interpreter options like node --eval or python -c that would execute arbitrary inline code from a semi-trusted config. If no script path can be detected (parser.script absent and no arg matching *.py|mjs|js|sh), this error is thrown.","triggerScenarios":"Entry like parser: {command: python3, args: [\"-c\", \"import ...\"]} or {command: node, args: [\"--eval\", \"...\"]} — scriptPath is null because parser.script is unset and no arg matches the script extension heuristic.","commonSituations":"Porting a shell one-liner into portals.yml; using python -c / node -e for quick scraping; a script whose extension isn't .py/.mjs/.js/.sh (e.g. .tsx or extensionless) so getParserScriptPath() misses it; forgetting the `script:` key when the script arg also carries placeholders.","solutions":["Set parser.script explicitly to the in-repo script path, e.g. parser: {command: python3, script: parsers/jobs.py, args: [...]}.","Rename the script so it has a recognized extension (.py, .mjs, .js, .sh) so it can be auto-detected from args.","Replace inline code (python -c, node -e) with a committed in-repo script file.","Remember the script must also be the interpreter's first argument (see the companion first-argument error)."],"exampleFix":"// before (portals.yml)\nparser: {command: node, args: [\"--eval\", \"fetch('https://x').then(r=>r.text()).then(console.log)\"]}\n// after\nparser: {command: node, script: parsers/fetch-jobs.mjs}","handlingStrategy":"validation","validationCode":"const scriptExts = /\\.(py|mjs|js|sh)$/;\nconst hasScript = Boolean(entry.parser?.script) || (entry.parser?.args || []).some(a => !String(a).startsWith('-') && scriptExts.test(String(a)));\nif (!String(entry.parser?.command || '').includes('/') && hasScript === false) {\n  throw new Error(`${entry.name}: interpreter command needs parser.script or a *.py/.mjs/.js/.sh arg`);\n}","typeGuard":null,"tryCatchPattern":"try {\n  await localParser.fetch(entry);\n} catch (e) {\n  if (String(e.message).includes('interpreter command requires an in-repo parser script')) {\n    console.error(`${entry.name}: no inline code — set parser.script to an in-repo file`);\n    return [];\n  }\n  throw e;\n}","preventionTips":["Always set parser.script explicitly instead of relying on extension auto-detection.","Never put inline code (python -c, node -e) in portals.yml.","Name parser files with recognized extensions (.py, .mjs, .js, .sh).","Run detect() over all entries in CI to catch unresolvable invocations early."],"tags":["security","config","validation"],"backgroundTag":"invalid-config-value","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"}