{"record":{"id":"5038e568f13ec38b","repo":"santifer/career-ops","slug":"existing-candidates-file-at-candidatespath-is-n","errorCode":null,"errorMessage":"Existing candidates file at ${candidatesPath} is not a JSON array","messagePattern":"Existing candidates file at (.+?) is not a JSON array","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"paste-reply.mjs","lineNumber":123,"sourceCode":"  };\n}\n\n/**\n * Append a candidate to the candidates JSON file, creating the file/array if\n * missing, without disturbing any existing entries. Exported for direct unit\n * testing. Returns the total candidate count after the append.\n */\nexport function appendCandidate(candidate, candidatesPath = CANDIDATES_PATH) {\n  let candidates = [];\n  if (fs.existsSync(candidatesPath)) {\n    let parsed;\n    try {\n      parsed = JSON.parse(fs.readFileSync(candidatesPath, 'utf-8'));\n    } catch (e) {\n      throw new Error(`Could not parse existing candidates file at ${candidatesPath}: ${e.message}`);\n    }\n    if (!Array.isArray(parsed)) {\n      throw new Error(`Existing candidates file at ${candidatesPath} is not a JSON array`);\n    }\n    candidates = parsed;\n  } else {\n    fs.mkdirSync(path.dirname(candidatesPath), { recursive: true });\n  }\n  candidates.push(candidate);\n  // Write-then-rename so an interrupted write (crash, signal, disk full)\n  // can never leave the real candidates file truncated/corrupted.\n  const tmpPath = `${candidatesPath}.tmp`;\n  fs.writeFileSync(tmpPath, JSON.stringify(candidates, null, 2), 'utf-8');\n  fs.renameSync(tmpPath, candidatesPath);\n  return candidates.length;\n}\n\n// Collect subject/from/body from stdin via a single readline.Interface and a\n// tiny manual state machine driven off its 'line' event.\n//\n// Earlier drafts chained `rl.question()` calls (one interface per prompt, or","sourceCodeStart":105,"sourceCodeEnd":141,"githubUrl":"https://github.com/santifer/career-ops/blob/9b17a8ac97b398a496b38e423ae24e433b43254f/paste-reply.mjs#L105-L141","documentation":"Thrown by appendCandidate() when data/reply-candidates.json parses successfully but the top-level value is not a JSON array (e.g. an object, a string, a number). The contract is an array of candidate objects; a non-array base would break every downstream reader of reply-candidates.json.","triggerScenarios":"The file was overwritten with a single JSON object instead of an array; someone saved `{...}` or a bare string/number as the top-level value; a serialization bug elsewhere wrote the wrong shape.","commonSituations":"External tool wrote a wrapped object ({candidates:[...]}) instead of the bare array; a test fixture used the wrong shape; an older code path serialized differently.","solutions":["Inspect the file's top-level structure and convert it to a plain array of candidate objects.","If unsure of the intended contents, back up the file and replace it with an empty array [].","Confirm no other writer is serializing a non-array shape.","Add a guard in your own writers to always pass arrays to JSON.stringify."],"exampleFix":"// before: top-level object\n{ \"last\": { \"company\": \"Acme\" } }\n// after: top-level array\n[ { \"company\": \"Acme\" } ]","handlingStrategy":"type-guard","validationCode":"import { readFileSync } from 'fs';\nfunction candidatesIsArray(p) {\n  try { return Array.isArray(JSON.parse(readFileSync(p, 'utf8'))); }\n  catch { return false; }\n}","typeGuard":"/** Confirms the candidates file's top-level value is the expected array shape. */\nfunction candidatesIsArray(p) {\n  try {\n    const v = JSON.parse(readFileSync(p, 'utf8'));\n    return Array.isArray(v);\n  } catch {\n    return false;\n  }\n}","tryCatchPattern":"try {\n  appendCandidate(c);\n} catch (e) {\n  if (/is not a JSON array/.test(e.message)) {\n    // rewrite file as a valid array (with old contents wrapped if recoverable)\n  } else throw e;\n}","preventionTips":["Ensure every writer of reply-candidates.json serializes a bare array.","Add a schema test that asserts the file's top-level is an array.","Reject wrapped-object shapes at the data boundary."],"tags":["json","schema","paste-reply","data-integrity"],"backgroundTag":null,"analyzedSha":"9b17a8ac97b398a496b38e423ae24e433b43254f","analyzedAt":"2026-08-13T00:48:39.135Z","schemaVersion":2},"datasetVersion":"2026-08-13T04:17:16.726Z"}