santifer/career-ops · error · Error
payload must include at least one of: cv, articleDigest
Error message
payload must include at least one of: cv, articleDigest
What it means
applyAdd() is the pure core of career-ops' `add` mode: given current cv.md / article-digest.md contents plus a payload, it computes the new contents. It refuses payloads that contain neither a `cv` nor an `articleDigest` key, since there would be nothing to insert into either target file. The throw is a fail-fast argument validation before any text transformation happens.
Solutions
- Pass a payload object with at least one of the two keys set: applyAdd({ cv: {...} } or applyAdd({ articleDigest: {...} }).
- Check the payload key spelling — `cv` and `articleDigest` are exact; nested CV content belongs under payload.cv as { section, dedupKey, entry }.
- Verify the code path that constructs the payload actually assigns it (an object built conditionally may stay empty when a flag is absent).
Example fix
// before
const payload = {};
if (addCv) payload.cvSection = { section: 'Projects', entry };
await applyAdd(payload, { cvText });
// after
const payload = {};
if (addCv) payload.cv = { section: 'Projects', dedupKey: 'proj:cv-santiago', entry };
await applyAdd(payload, { cvText }); Defensive patterns
Strategy: validation
Validate before calling
function canCallApplyAdd(payload) {
return payload != null && typeof payload === 'object' && (Boolean(payload.cv) || Boolean(payload.articleDigest));
} Type guard
function isAddPayload(p) {
return typeof p === 'object' && p !== null && ('cv' in p || 'articleDigest' in p);
} Try / catch
try {
applyAdd(payload, { cvText, articleText });
} catch (e) {
if (e.message.startsWith('payload must include')) {
console.error('Nothing to add: set payload.cv or payload.articleDigest');
} else throw e;
} Prevention
- Construct payloads through a single builder function that validates keys at construction time.
- Assert the payload is non-empty in tests before calling applyAdd.
- Never rename the top-level keys cv/articleDigest when refactoring.
When it happens
Trigger: Calling applyAdd(null), applyAdd('string'), applyAdd({}), or applyAdd({ cv: null, articleDigest: undefined }) — any call where payload is not a non-null object with a truthy cv or articleDigest property.
Common situations: Building the payload conditionally (e.g. `const payload = {}; if (opts.toCv) payload.cvSection = ...` using the wrong key name), a CLI flag parser producing an empty options object, or passing parsed JSON whose keys were renamed between versions of the add-entry API.
Understand the failure class
Background: "missing required argument" and "the following required arguments were not provided": what required-argument errors mean and how to fix them — this error's family across 20 libraries.
Related errors
- getEmployerProfile: id is required
- 4dayweek: invalid URL
- 4dayweek: URL must use HTTPS
- a 40-hex commit --sha is required
- apify: entry has invalid field_map. Each of title, url…
AI-assisted analysis of santifer/career-ops@aac998c7ed (2026-09-16).
Data as JSON: /api/errors/448cce541191f41b.
Report an issue: GitHub.
Appendix: source
Thrown at add-entry.mjs:171
}
return false;
}
export function appendArticleDigest(md, entry) {
const block = entry.replace(/\s+$/, '');
const base = md.replace(/\s+$/, '');
// Keep the existing `---`-separated block rhythm.
return `${base}\n\n---\n\n${block}\n`;
}
/**
* Pure core: given the current file contents and a payload, compute the new
* contents and a per-target status. No I/O — this is what the tests exercise.
* @returns {{ cv: string, articleDigest: string, result: object }}
*/
export function applyAdd(payload, { cvText = null, articleText = null } = {}) {
if (!payload || typeof payload !== 'object' || (!payload.cv && !payload.articleDigest)) {
throw new Error('payload must include at least one of: cv, articleDigest');
}
const result = {};
let cv = cvText;
let articleDigest = articleText;
if (payload.cv) {
const { section, dedupKey, entry } = payload.cv;
if (!section || !entry) throw new Error('payload.cv requires { section, entry }');
// dedupKey is what makes the insert idempotent — refuse to add without one
// rather than silently allowing duplicate re-runs.
if (!normalizeKey(dedupKey)) throw new Error('payload.cv requires a non-empty dedupKey (used for dedup/idempotency)');
if (cvText === null) throw new Error(`cv.md not found — cannot add to a CV that does not exist`);
if (cvHasEntry(cvText, section, dedupKey)) {
result.cv = { status: 'duplicate', section };
} else {
cv = insertIntoCvSection(cvText, section, entry);
result.cv = { status: 'added', section };View on GitHub (pinned to aac998c7ed)