pbakaus/impeccable · error

concept-seed: --candidate-count must be an integer from 5…

Error message

concept-seed: --candidate-count must be an integer from 5 to 7

What it means

Thrown by the concept-seed script when --candidate-count is not an integer between 5 and 7 inclusive. The value is read naively via args.indexOf('--candidate-count') followed by Number(args[i+1]), so a missing value yields Number(undefined) = NaN and the equals form --candidate-count=6 is not recognized at all. The 5-7 band is deliberate: fewer than 5 candidates starves the ranked roll, more than 7 bloats the model's consideration set. Default is 7 when the flag is omitted.

Solutions

  1. Pass a plain space-separated integer in range: `--candidate-count 6`.
  2. Omit the flag entirely to get the default of 7.
  3. If you used `--candidate-count=6`, rewrite it as `--candidate-count 6` — the equals form is silently parsed as NaN.
  4. When scripting, quote and default the variable: `--candidate-count "${COUNT:-7}"` after validating COUNT is 5, 6, or 7.

Example fix

# before
node skill/scripts/concept-seed.mjs --scope direction --candidate-count=6   # NaN -> throws
node skill/scripts/concept-seed.mjs --scope direction --candidate-count    # value missing -> throws

# after
node skill/scripts/concept-seed.mjs --scope direction --candidate-count 6
node skill/scripts/concept-seed.mjs --scope direction                      # default 7
Defensive patterns

Strategy: validation

Validate before calling

const SEED_COUNT_RE = /^[5-7]$/;
function validCandidateCount(raw) {
  return raw == null || (typeof raw === 'string' && SEED_COUNT_RE.test(raw.trim()));
}
// before shelling out:
if (!validCandidateCount(process.env.CANDIDATE_COUNT)) {
  console.error('--candidate-count must be 5, 6, or 7 (space-separated)');
  process.exit(2);
}
spawnSync('node', ['skill/scripts/concept-seed.mjs', '--scope', 'direction', '--candidate-count', COUNT]);

Type guard

function isValidSeedCount(v) {
  return v == null || (typeof v === 'number' && Number.isInteger(v) && v >= 5 && v <= 7);
}

Prevention

When it happens

Trigger: Running `node skill/scripts/concept-seed.mjs --scope direction --candidate-count 4` (or 8, or 5.5, or 'six'); passing `--candidate-count` as the last argument (next token undefined -> NaN); using `--candidate-count=6` (the indexOf-based parser does not recognize the = form, so it reads the next token or undefined); passing a value like '6x' that Number() coerces to NaN.

Common situations: Agent or user tuning the seed hand size; copy-pasting a command template that used = syntax; truncating the command line so the value falls off; scripting the call and interpolating an empty variable (`--candidate-count $COUNT` with COUNT unset).

Related errors


AI-assisted analysis of pbakaus/impeccable@f88b2837a7 (2026-08-18). Data as JSON: /api/errors/2b8d969a1c76e5a3. Report an issue: GitHub.

Appendix: source

Thrown at skill/scripts/concept-seed.mjs:333

  if (register !== null && reroll < 1) {
    throw new Error('concept-seed: --register steers a re-roll round; pass --reroll <n> with it');
  }
  if (register !== null && scope !== 'direction') {
    throw new Error('concept-seed: --register applies to direction rounds only');
  }
  if (mode !== null && !SEED_MODES.has(mode)) {
    throw new Error('concept-seed: --mode must be persuade, operate, read, or experience');
  }
  // Grain needs no mode: how much of the product is in play is independent of
  // which register of work it is.
  if (grain !== null && !COMPOSITION_GRAINS.includes(grain)) {
    throw new Error(`concept-seed: --grain must be one of ${COMPOSITION_GRAINS.join(', ')}`);
  }
  if (platform !== null && !COMPOSITION_PLATFORMS.includes(platform)) {
    throw new Error(`concept-seed: --platform must be one of ${COMPOSITION_PLATFORMS.join(', ')}`);
  }
  if (!Number.isInteger(candidateCount) || candidateCount < 5 || candidateCount > 7) {
    throw new Error('concept-seed: --candidate-count must be an integer from 5 to 7');
  }
  const unit = (salt) => {
    const h = crypto.createHash('sha256').update(`${scope}:${salt}:${key}`).digest();
    return h.readUInt32BE(0) / 0xffffffff;
  };
  const indexSalt = reroll === 0 ? 'index' : `index:reroll-${reroll}`;
  const buildIndex = 3 + Math.floor(unit(indexSalt) * (candidateCount - 2)); // 3..candidateCount
  // Surface scope deals a hand of three grounded structures: one card is not
  // a choice, and the full ranked list would hand selection back to the
  // model's taste. The dice pick all three; the primary index leads. The
  // no-lineup rule stays direction-only, where it was written for worlds.
  const dealtIndices = [buildIndex];
  for (let draw = 0; scope === 'surface' && dealtIndices.length < Math.min(3, candidateCount); draw += 1) {
    const idx = 1 + Math.floor(unit(`${indexSalt}:deal-${draw}`) * candidateCount);
    if (!dealtIndices.includes(idx)) dealtIndices.push(idx);
    if (draw > 64) { // hash repeats cannot stall the deal
      for (let fill = 1; dealtIndices.length < Math.min(3, candidateCount); fill += 1) {
        if (!dealtIndices.includes(fill)) dealtIndices.push(fill);

View on GitHub (pinned to f88b2837a7)