santifer/career-ops · error · Error
Could not parse existing candidates file at
Error message
Could not parse existing candidates file at ${candidatesPath}: ${e.message} What it means
appendCandidate appends a reply-candidate entry to data/reply-candidates.json, an array file. If the existing file exists but is not valid JSON, it throws with the path and the underlying SyntaxError message rather than silently truncating the user's data.
Solutions
- Open the file at candidatesPath and fix the JSON syntax error reported in e.message
- Validate with `node -e "JSON.parse(require('fs').readFileSync('data/reply-candidates.json','utf8'))"` to locate the error
- If unrecoverable, back up the file and rebuild it as a valid JSON array of the entries you want to keep
- Re-run the paste-reply command after the file parses
Example fix
// before
appendCandidate(candidate); // throws on corrupt file
// after
try {
appendCandidate(candidate);
} catch (e) {
if (e.message.startsWith('Could not parse existing candidates file')) {
console.error('Fix or restore data/reply-candidates.json first:', e.message);
}
} Defensive patterns
Strategy: validation
Validate before calling
function canParseJsonArray(p) { try { return Array.isArray(JSON.parse(readFileSync(p, 'utf-8'))); } catch { return false; } } Try / catch
try { appendCandidate(c); } catch (e) { if (e.message.includes('Could not parse existing candidates file')) { backup(candidatesPath); console.error('Repair data/reply-candidates.json before appending'); } else throw e; } Prevention
- Never hand-edit reply-candidates.json without re-validating JSON afterward
- Use a JSON editor/validator when touching the file
- Back up the file before bulk operations
- Kill-write safely: write to a temp file and rename atomically in your own tooling
When it happens
Trigger: appendCandidate(candidate) called while data/reply-candidates.json contains malformed JSON — truncated write, hand-edited syntax error, concatenation of two runs, encoding corruption.
Common situations: A previous paste-reply run was killed mid-write; the user manually edited the JSON and left a trailing comma; a merge of two files produced invalid syntax; the file was written by another tool with non-JSON content.
Understand the failure class
Background: JSON parse error: "Unexpected token" / "not valid JSON" / "failed to parse" — what JSON parsers are really complaining about — this error's family across 45 libraries.
Related errors
- collage: unrecognized response envelope (expected a…
- cv.md not found — cannot add to a CV that does not exist
- each entry needs a string "input" and "output"
- Existing candidates file at
- file not found
AI-assisted analysis of santifer/career-ops@aac998c7ed (2026-09-16).
Data as JSON: /api/errors/1b0a6e07aedf2061.
Report an issue: GitHub.
Appendix: source
Thrown at paste-reply.mjs:122
// Classification is reply-watch.mjs's job, not this script's — leaving
// signal unset is safe (see file header comment for why).
signal: null,
};
}
/**
* Append a candidate to the candidates JSON file, creating the file/array if
* missing, without disturbing any existing entries. Exported for direct unit
* testing. Returns the total candidate count after the append.
*/
export function appendCandidate(candidate, candidatesPath = CANDIDATES_PATH) {
let candidates = [];
if (fs.existsSync(candidatesPath)) {
let parsed;
try {
parsed = JSON.parse(fs.readFileSync(candidatesPath, 'utf-8'));
} catch (e) {
throw new Error(`Could not parse existing candidates file at ${candidatesPath}: ${e.message}`);
}
if (!Array.isArray(parsed)) {
throw new Error(`Existing candidates file at ${candidatesPath} is not a JSON array`);
}
candidates = parsed;
} else {
fs.mkdirSync(path.dirname(candidatesPath), { recursive: true });
}
candidates.push(candidate);
// Write-then-rename so an interrupted write (crash, signal, disk full)
// can never leave the real candidates file truncated/corrupted.
const tmpPath = `${candidatesPath}.tmp`;
fs.writeFileSync(tmpPath, JSON.stringify(candidates, null, 2), 'utf-8');
renameSyncWithRetry(tmpPath, candidatesPath);
return candidates.length;
}
// Collect subject/from/body from stdin via a single readline.Interface and aView on GitHub (pinned to aac998c7ed)