santifer/career-ops · warning

⚠️ config/profile.yml cv.sections lists

Error message

⚠️  config/profile.yml cv.sections lists "${shown}" more than once — keeping its first position.

What it means

While applying the cv.sections order from config/profile.yml, generate-pdf.mjs iterates the requested names and de-duplicates them. A name appearing more than once is redundant (the second occurrence has no effect), so it warns, keeps the first position, and skips the duplicate via `continue`.

Solutions

  1. Remove the duplicate entry from cv.sections, keeping one occurrence at the desired position
  2. Re-run PDF generation; the first occurrence's position is already respected, so output will not change
  3. If generating config programmatically, de-duplicate while preserving first-seen order (new Set(list))

Example fix

# before: config/profile.yml
cv:
  sections: [experience, skills, experience]
# after
cv:
  sections: [experience, skills]
Defensive patterns

Strategy: validation

Validate before calling

const sections = profile.cv?.sections ?? [];
const dupes = sections.filter((s, i) => sections.indexOf(s) !== i);
if (dupes.length) console.warn('duplicate cv.sections entries:', [...new Set(dupes)]);

Type guard

const hasDuplicateSections = (order) => new Set(order).size !== order.length;

Prevention

When it happens

Trigger: `cv.sections` in config/profile.yml lists the same section key twice, e.g. `['experience','skills','experience']`. The seen Set detects the repeat at generate-pdf.mjs:940 and emits this warning before continuing.

Common situations: Copy-paste editing of the sections list; merging two people's config snippets; scripted config generation appending defaults to user choices.

Understand the failure class

Background: "Invalid value" and "allowed values are" config errors: what your library rejected and how to fix it — this error's family across 41 libraries.

Related errors


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

Appendix: source

Thrown at generate-pdf.mjs:941

  const { blocks, ambiguous, unrecognized } = extractSectionBlocks(html);

  const byKey = new Map();
  for (const block of blocks) {
    if (!byKey.has(block.key)) byKey.set(block.key, block);
  }

  const chosen = [];
  const unresolved = [];
  const seen = new Set();
  for (const name of order) {
    // `name` comes from a config file and is quoted straight into console
    // output, so it gets the same treatment as a rendered title: displayTitle()
    // strips C0/C1 controls and bidi overrides (either can repaint a terminal
    // line so a warning appears to say something the tool never printed) and
    // bounds the length so one long entry cannot bury the message.
    const shown = displayTitle(name);
    if (seen.has(name)) {
      console.warn(`⚠️  config/profile.yml cv.sections lists "${shown}" more than once — keeping its first position.`);
      continue;
    }
    seen.add(name);
    if (!CV_SECTION_KEYS.includes(name)) {
      console.warn(`⚠️  config/profile.yml cv.sections lists "${shown}", which is not a CV section — ignoring it. Recognized: ${CV_SECTION_KEYS.join(', ')}.`);
      continue;
    }
    const block = byKey.get(name);
    if (block) {
      chosen.push(block);
    } else if (ambiguous.has(name)) {
      // Present, but its markup doesn't stand on its own — it opens or closes
      // elements outside itself, so moving it would leave tags unbalanced.
      // Reported rather than skipped quietly: the section stays where the
      // template put it, and the user would otherwise see a setting that
      // silently does nothing.
      console.warn(`⚠️  config/profile.yml cv.sections lists "${shown}", but this CV's markup does not enclose that section on its own — leaving it in place, because moving it would leave tags unbalanced.`);
    } else {

View on GitHub (pinned to aac998c7ed)