santifer/career-ops · error

CV section order diverges from cv.md: rendered

Error message

CV section order diverges from cv.md: rendered ${renderedOrder}; cv.md ${sourceOrder}

What it means

validateCvSectionOrder compares the section order of the rendered CV HTML against the heading order declared in cv.md (and, as a second chance, the canonical tailored order). If the rendered relative order matches neither, it throws — unless --allow-reorder was set, which downgrades the throw to a warning. It guards against an agent accidentally scrambling CV sections during tailoring.

Solutions

  1. Re-order the CV sections to match cv.md's heading order (or the canonical tailored order), then regenerate
  2. If the reorder is deliberate (e.g. Projects before Education for a technical role), re-run with --allow-reorder (or set allowReorder in the config/profile flow) to downgrade to a warning
  3. Check cv.md's heading order matches what you intend — the source of truth may itself be stale
  4. Inspect the rendered HTML section-title order against the order printed in the error message
  5. Set cv.sections in config/profile.yml so reorderCvSections applies the intended order before validation

Example fix

$ node generate-pdf.mjs --in cv.html --out cv.pdf
Error: CV section order diverges from cv.md: rendered Education -> Projects -> Experience; cv.md Experience -> Projects -> Education

# before
$ node generate-pdf.mjs --in cv.html --out cv.pdf
# after (deliberate reorder)
$ node generate-pdf.mjs --in cv.html --out cv.pdf --allow-reorder
Defensive patterns

Strategy: validation

Validate before calling

import { readFileSync } from 'node:fs';
const cv = readFileSync('cv.md', 'utf-8');
const headings = [...cv.matchAll(/^##\s+(.+)$/gm)].map(m => m[1].trim().toLowerCase());
const allowedOrders = [
  ['summary','competencies','experience','projects','education','certifications','awards','interests','skills'],
];
// verify the rendered section order matches cv.md headings or the canonical tailored order before generating

Type guard

const orderMatches = (renderedKeys, referenceKeys) =>
  renderedKeys.length >= 2 &&
  renderedKeys.every((k, i) => i === 0 || referenceKeys.indexOf(k) > referenceKeys.indexOf(renderedKeys[i - 1]));

Try / catch

try {
  validateCvSectionOrder(html, cvMarkdown);
} catch (err) {
  if (err.message.startsWith('CV section order diverges')) {
    console.warn(err.message, '— pass --allow-reorder if this reorder is deliberate');
    return; // or rethrow in CI
  }
  throw err;
}

Prevention

When it happens

Trigger: Rendering a tailored CV whose sections were reordered in a way that breaks both cv.md's order and the canonical tailored order (summary, competencies, experience, projects, education, certifications, awards, interests, skills) without passing --allow-reorder; or editing the HTML template/reorder step so sections come out shuffled.

Common situations: An agent hand-tailors a CV moving e.g. Awards before Experience in an order that matches neither reference; a custom template emits sections in a new order; a deliberate reorder done by the user without knowing the --allow-reorder escape hatch exists.

Understand the failure class

Background: Schema validation failed / invalid input schema: payload rejected because its shape doesn't match the expected schema — this error's family across 28 libraries.

Related errors


AI-assisted analysis of santifer/career-ops@aac998c7ed (2026-09-16). Data as JSON: /api/errors/3d505bacb47d77d2. Report an issue: GitHub.

Appendix: source

Thrown at generate-pdf.mjs:491

  // CV tailored per modes/pdf.md's documented order looks like. Only treat it
  // as a real problem if it ALSO fails to match that canonical order.
  const canonicalComparable = rendered.filter(section => CANONICAL_TAILORED_POSITIONS.has(section.key));
  if (canonicalComparable.length >= 2
      && findOrderDivergence(canonicalComparable, CANONICAL_TAILORED_POSITIONS) === -1) {
    return;
  }

  const renderedOrder = renderedComparable.map(section => section.title).join(' -> ');
  const sourceOrder = source
    .filter(section => renderedComparable.some(renderedSection => renderedSection.key === section.key))
    .map(section => section.title)
    .join(' -> ');
  const message = `CV section order diverges from cv.md: rendered ${renderedOrder}; cv.md ${sourceOrder}`;
  if (allowReorder) {
    console.warn(`⚠️  ${message} (proceeding — --allow-reorder set)`);
    return;
  }
  throw new Error(message);
}

/**
 * Every canonical section key the alias table can produce, in template order.
 * Derived from the table rather than restated so the two cannot drift.
 */
export const CV_SECTION_KEYS = [...new Set(SECTION_ALIASES.values())];

// The all-caps comments the templates use to delimit sections, matched exactly
// as cv-sections-core.mjs matches them when stripping empty sections.
const SECTION_MARKER_RE = /<!--\s+[A-Z][A-Z ]*-->/g;
const SECTION_TITLE_RE = /class=["'][^"']*\bsection-title\b[^"']*["'][^>]*>([\s\S]*?)<\/[^>]+>/gi;

const DISPLAY_TITLE_MAX = 60;

/**
 * A rendered section title, made safe to quote back in a console warning.
 *

View on GitHub (pinned to aac998c7ed)