santifer/career-ops · error · Error

each entry needs a string "input" and "output"

Error message

each entry needs a string "input" and "output"

What it means

In the batch render loop, every manifest entry must be an object with string `input` and `output`. Anything else — null, a bare string, numbers, or objects missing either key — throws this per-entry error, which is recorded as that entry's failure (the batch continues with remaining entries). input/output are the only mandatory fields; format and reportNum are optional.

Source

Thrown at generate-pdf.mjs:1261

  // Prepare each entry. Preserve input order in the results by carrying the
  // manifest index through renderBatch; prep failures land at their own index.
  let cvMarkdown = '';
  try {
    cvMarkdown = await readFile(resolve(workspaceRoot, 'cv.md'), 'utf-8');
  } catch (err) {
    if (err?.code !== 'ENOENT') throw err;
  }
  // One profile governs the whole batch, so the declared order is read once
  // rather than per entry. Anchored to workspaceRoot for the same reason the
  // single render is: it is the anchor readStyleTokens() and the cv.md read
  // already use, so one profile.yml supplies every setting.
  const cvSectionOrder = readCvSectionOrder(resolve(workspaceRoot, 'config', 'profile.yml'));

  for (let i = 0; i < manifest.length; i++) {
    const spec = manifest[i];
    try {
      if (!spec || typeof spec.input !== 'string' || typeof spec.output !== 'string') {
        throw new Error('each entry needs a string "input" and "output"');
      }

      const entryFormat = (spec.format || globals.format).toLowerCase();
      if (!validFormats.includes(entryFormat)) {
        throw new Error(`invalid format "${entryFormat}" (use: ${validFormats.join(', ')})`);
      }

      const entryReport = (spec.reportNum ?? '').toString().trim();
      if (entryReport && !/^\d+$/.test(entryReport)) {
        throw new Error(`invalid reportNum "${entryReport}" (use the numeric report number)`);
      }

      // Resolve manifest-supplied input/output relative to the manifest's own
      // directory, not process.cwd(), so a manifest renders identically wherever
      // the batch is launched from. Absolute paths in the manifest still win
      // (resolve() ignores the base when the tail is absolute).
      const entryInput = resolve(manifestDir, spec.input);
      const entryOutput = resolve(manifestDir, spec.output);

View on GitHub (pinned to 60398d6549)

Solutions

  1. Open the manifest at the failing index (the batch reports which entry failed) and give it both keys: {"input": "output/x.html", "output": "output/x.pdf"}.
  2. Fix generator code so optional values are filtered out rather than serialized as undefined-dropped half-entries.
  3. Lint the manifest before running: every element must be an object whose typeof input/output is 'string'.

Example fix

// before (manifest.json)
[
  { "input": "output/a.html", "output": "output/a.pdf" },
  { "output": "output/b.pdf" }
]

// after
[
  { "input": "output/a.html", "output": "output/a.pdf" },
  { "input": "output/b.html", "output": "output/b.pdf" }
]
Defensive patterns

Strategy: validation

Validate before calling

const manifest = JSON.parse(readFileSync(manifestPath, 'utf-8'));
const bad = manifest
  .map((e, i) => (!e || typeof e.input !== 'string' || typeof e.output !== 'string') ? i : -1)
  .filter(i => i >= 0);
if (bad.length) { console.error(`Manifest entries missing string input/output at index: ${bad.join(', ')}`); process.exit(1); }

Type guard

/** @param {unknown} e */
function isManifestEntry(e) {
  return typeof e === 'object' && e !== null
    && typeof /** @type {any} */ (e).input === 'string'
    && typeof /** @type {any} */ (e).output === 'string';
}

Prevention

When it happens

Trigger: A JSON manifest where one entry is a leftover string ('see above'), or a template-variable miss produced {input: undefined} that JSON.stringify dropped, leaving an object with only output; hand-written manifest with a typo'd key (in/out instead of input/output).

Common situations: Manifests generated by scripts joining partial data; copy-paste editing introducing inconsistent entries; version drift where an old manifest schema used different key names.

Understand the failure class

Background: "Missing required field" and "field is required" errors: why libraries reject payloads that omit mandatory fields — this error's family across 20 libraries.

Related errors


AI-assisted analysis of santifer/career-ops@60398d6549 (2026-08-20). Data as JSON: /api/errors/736457daab2dec5d. Report an issue: GitHub.