santifer/career-ops · error · Error

local-parser: the parser script must be the interpreter's…

Error message

local-parser: the parser script must be the interpreter's first argument

What it means

For a whitelisted interpreter command, the in-repo parser script must be args[0]. Anything before the script could be an interpreter option (node --require, python -c) that executes arbitrary code, so resolveInvocation() enforces the script leads the argument list.

Solutions

  1. Reorder parser.args so the script path is the first element; pass interpreter flags via the command's supported config or drop them.
  2. If parser.script is set and args also includes the script, remove the duplicate and keep args[0] equal to the script path string.
  3. Verify after {careers_url}/{company} expansion that args[0] still string-equals the script path (expansion can change the string).
  4. Use the direct in-repo-file form instead (command: parsers/jobs.py style) if you don't need interpreter options — no shebang/exec bit needed only for interpreters, so prefer keeping the interpreter with the script first.

Example fix

// before (portals.yml)
parser: {command: python3, script: parsers/jobs.py, args: ["-u", "parsers/jobs.py"]}
// after
parser: {command: python3, script: parsers/jobs.py, args: ["parsers/jobs.py", "-u"]}
Defensive patterns

Strategy: validation

Validate before calling

const scriptPath = String(entry.parser?.script || (entry.parser?.args || []).find(a => /\.(py|mjs|js|sh)$/.test(String(a))) || '');
const first = String((entry.parser?.args || [])[0] || '');
if (scriptPath && first !== scriptPath) throw new Error(`${entry.name}: script must be parser.args[0]`);

Try / catch

try {
  await localParser.fetch(entry);
} catch (e) {
  if (String(e.message).includes("must be the interpreter's first argument")) {
    console.error(`${entry.name}: reorder parser.args so the script path comes first`);
    return [];
  }
  throw e;
}

Prevention

When it happens

Trigger: Entry where a script is detected (via parser.script or a *.py/.mjs/.js/.sh arg) but the expanded args array does not start with it — e.g. {command: python3, script: parsers/jobs.py, args: ["-u", "parsers/jobs.py"]} puts '-u' first.

Common situations: Adding interpreter flags (-u, --harmony, -O) before the script path; parser.script set while parser.args also repeats the script after some flag; placeholder expansion reordering assumptions; copying a shell invocation verbatim into args.

Understand the failure class

Background: "Invalid value" and "allowed values are" config errors: what your library rejected and how to fix it — this error's family across 41 libraries.

Related errors


AI-assisted analysis of santifer/career-ops@aac998c7ed (2026-09-16). Data as JSON: /api/errors/b1e5e743d5b35389. Report an issue: GitHub.

Appendix: source

Thrown at providers/local-parser.mjs:114

  return resolveInsideRoot(value);
}

// Validate the whole invocation and return what to spawn. Throws on anything unsafe.
function resolveInvocation(entry) {
  const rawCommand = String(entry.parser?.command || '');
  const command = resolveCommand(rawCommand);
  const args = buildParserArgs(entry);
  const scriptPath = getParserScriptPath(entry);

  const usesInterpreter = !rawCommand.includes('/') && ALLOWED_INTERPRETERS.has(rawCommand);
  if (usesInterpreter) {
    // A whitelisted interpreter must run an in-repo script as its FIRST argument.
    // Anything before the script is an interpreter option (node --eval / --require,
    // python -c, …) that could execute arbitrary code, so require the script to lead.
    if (!scriptPath) throw new Error('local-parser: interpreter command requires an in-repo parser script');
    resolveInsideRoot(scriptPath);
    if (args[0] !== scriptPath) {
      throw new Error('local-parser: the parser script must be the interpreter\'s first argument');
    }
  } else if (scriptPath) {
    // command is an in-repo file; keep any detected script path inside the repo too.
    resolveInsideRoot(scriptPath);
  }

  return { command, args };
}

function normalizeJobUrl(rawUrl, baseUrl) {
  if (!rawUrl) return '';
  try {
    return new URL(String(rawUrl).trim(), baseUrl || undefined).href;
  } catch {
    return '';
  }
}

View on GitHub (pinned to aac998c7ed)