affaan-m/ECC · error · Error

Invalid warn threshold

Error message

Invalid warn threshold: ${options.warnThreshold}

What it means

collectSkillHealth validates options.warnThreshold, the fraction (0..1 scale) of stale executions at which a skill is flagged as unhealthy. The value must be a finite number >= 0; strings that Number() cannot convert to a finite non-negative value, NaN, Infinity, and negatives all throw this error.

Solutions

  1. Pass a finite non-negative number, e.g. collectSkillHealth({ warnThreshold: 0.1 }).
  2. Parse percentages at the boundary: Number(value.replace('%','')) / 100 before calling.
  3. Guard in the caller: const t = Number(raw); if (!Number.isFinite(t) || t < 0) reject before invoking.
  4. Omit warnThreshold to use the default 0.1.

Example fix

// before
health.collectSkillHealth({ warnThreshold: '10%' });

// after
const raw = '10%';
health.collectSkillHealth({ warnThreshold: Number(raw.replace('%', '')) / 100 }); // 0.1
Defensive patterns

Strategy: validation

Validate before calling

function toWarnThreshold(raw) {
  const n = typeof raw === 'string' && raw.endsWith('%') ? Number(raw.slice(0, -1)) / 100 : Number(raw);
  if (!Number.isFinite(n) || n < 0) throw new Error(`warnThreshold must be a finite number >= 0, got: ${raw}`);
  return n;
}

Type guard

const isValidThreshold = (v) => typeof v === 'number' && Number.isFinite(v) && v >= 0;

Try / catch

try {
  report = health.collectSkillHealth({ warnThreshold: raw });
} catch (err) {
  if (err.message.startsWith('Invalid warn threshold')) {
    console.error(`Bad --warn-threshold '${raw}'; using default 0.1.`);
    report = health.collectSkillHealth({});
  } else throw err;
}

Prevention

When it happens

Trigger: collectSkillHealth({ warnThreshold: 'high' }); passing warnThreshold: -0.5; passing NaN or Infinity; passing an object/boolean that Number() coerces to NaN (e.g. {} or undefined-like strings where Number('' || 0.1) traps differ from intent).

Common situations: CLI --warn-threshold flags passed as raw unparsed strings ('--warn-threshold 0.2' forwarded as ' 0.2 ' with whitespace is fine, but 'twenty percent' is not); config files with percentage strings like '10%' instead of 0.1; copy-pasted negative or typo'd values.

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 affaan-m/ECC@8321021c54 (2026-09-16). Data as JSON: /api/errors/239c07da63f8414f. Report an issue: GitHub.

Appendix: source

Thrown at scripts/lib/skill-evolution/health.js:156

      timeMs: Date.parse(record.recorded_at),
    }))
    .filter(entry => !Number.isNaN(entry.timeMs))
    .sort((left, right) => left.timeMs - right.timeMs)
    .at(-1)?.timestamp || null;
}

function collectSkillHealth(options = {}) {
  const now = options.now || new Date().toISOString();
  const nowMs = Date.parse(now);
  if (Number.isNaN(nowMs)) {
    throw new Error(`Invalid now timestamp: ${now}`);
  }

  const warnThreshold = typeof options.warnThreshold === 'number'
    ? options.warnThreshold
    : Number(options.warnThreshold || 0.1);
  if (!Number.isFinite(warnThreshold) || warnThreshold < 0) {
    throw new Error(`Invalid warn threshold: ${options.warnThreshold}`);
  }

  const records = tracker.readSkillExecutionRecords(options);
  const skillsById = discoverSkills(options);
  const recordsBySkill = records.reduce((groupedRecords, record) => {
    if (!groupedRecords.has(record.skill_id)) {
      groupedRecords.set(record.skill_id, []);
    }

    groupedRecords.get(record.skill_id).push(record);
    return groupedRecords;
  }, new Map());

  for (const skillId of recordsBySkill.keys()) {
    if (!skillsById.has(skillId)) {
      skillsById.set(skillId, {
        skill_id: skillId,
        skill_dir: null,

View on GitHub (pinned to 8321021c54)