{"record":{"id":"736457daab2dec5d","repo":"santifer/career-ops","slug":"each-entry-needs-a-string-input-and-output","errorCode":null,"errorMessage":"each entry needs a string \"input\" and \"output\"","messagePattern":"each entry needs a string \"input\" and \"output\"","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"generate-pdf.mjs","lineNumber":1261,"sourceCode":"  // Prepare each entry. Preserve input order in the results by carrying the\n  // manifest index through renderBatch; prep failures land at their own index.\n  let cvMarkdown = '';\n  try {\n    cvMarkdown = await readFile(resolve(workspaceRoot, 'cv.md'), 'utf-8');\n  } catch (err) {\n    if (err?.code !== 'ENOENT') throw err;\n  }\n  // One profile governs the whole batch, so the declared order is read once\n  // rather than per entry. Anchored to workspaceRoot for the same reason the\n  // single render is: it is the anchor readStyleTokens() and the cv.md read\n  // already use, so one profile.yml supplies every setting.\n  const cvSectionOrder = readCvSectionOrder(resolve(workspaceRoot, 'config', 'profile.yml'));\n\n  for (let i = 0; i < manifest.length; i++) {\n    const spec = manifest[i];\n    try {\n      if (!spec || typeof spec.input !== 'string' || typeof spec.output !== 'string') {\n        throw new Error('each entry needs a string \"input\" and \"output\"');\n      }\n\n      const entryFormat = (spec.format || globals.format).toLowerCase();\n      if (!validFormats.includes(entryFormat)) {\n        throw new Error(`invalid format \"${entryFormat}\" (use: ${validFormats.join(', ')})`);\n      }\n\n      const entryReport = (spec.reportNum ?? '').toString().trim();\n      if (entryReport && !/^\\d+$/.test(entryReport)) {\n        throw new Error(`invalid reportNum \"${entryReport}\" (use the numeric report number)`);\n      }\n\n      // Resolve manifest-supplied input/output relative to the manifest's own\n      // directory, not process.cwd(), so a manifest renders identically wherever\n      // the batch is launched from. Absolute paths in the manifest still win\n      // (resolve() ignores the base when the tail is absolute).\n      const entryInput = resolve(manifestDir, spec.input);\n      const entryOutput = resolve(manifestDir, spec.output);","sourceCodeStart":1243,"sourceCodeEnd":1279,"githubUrl":"https://github.com/santifer/career-ops/blob/60398d6549a46f5266929538af21cfab94badc75/generate-pdf.mjs#L1243-L1279","documentation":"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.","triggerScenarios":"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).","commonSituations":"Manifests generated by scripts joining partial data; copy-paste editing introducing inconsistent entries; version drift where an old manifest schema used different key names.","solutions":["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\"}.","Fix generator code so optional values are filtered out rather than serialized as undefined-dropped half-entries.","Lint the manifest before running: every element must be an object whose typeof input/output is 'string'."],"exampleFix":"// before (manifest.json)\n[\n  { \"input\": \"output/a.html\", \"output\": \"output/a.pdf\" },\n  { \"output\": \"output/b.pdf\" }\n]\n\n// after\n[\n  { \"input\": \"output/a.html\", \"output\": \"output/a.pdf\" },\n  { \"input\": \"output/b.html\", \"output\": \"output/b.pdf\" }\n]","handlingStrategy":"validation","validationCode":"const manifest = JSON.parse(readFileSync(manifestPath, 'utf-8'));\nconst bad = manifest\n  .map((e, i) => (!e || typeof e.input !== 'string' || typeof e.output !== 'string') ? i : -1)\n  .filter(i => i >= 0);\nif (bad.length) { console.error(`Manifest entries missing string input/output at index: ${bad.join(', ')}`); process.exit(1); }","typeGuard":"/** @param {unknown} e */\nfunction isManifestEntry(e) {\n  return typeof e === 'object' && e !== null\n    && typeof /** @type {any} */ (e).input === 'string'\n    && typeof /** @type {any} */ (e).output === 'string';\n}","tryCatchPattern":null,"preventionTips":["Validate the whole manifest with the guard above before launching a batch, not entry-by-entry at 2 a.m.","Generate manifests with a schema (JSON Schema / zod) so undefined fields fail at build time.","Keep a known-good manifest in the repo as the structural example."],"tags":["batch","manifest","validation","json"],"backgroundTag":"missing-required-field","analyzedSha":"60398d6549a46f5266929538af21cfab94badc75","analyzedAt":"2026-08-20T23:00:06.764Z","schemaVersion":2},"datasetVersion":"2026-08-21T13:17:26.733Z"}