affaan-m/ECC · error · Error

Invalid warn threshold: ${options.warnThreshold}

Error message

Invalid warn threshold: ${options.warnThreshold}

What it means

Thrown by collectSkillHealth when options.warnThreshold, after Number() coercion (default 0.1), is not finite or is negative. warnThreshold is the fraction below which a skill's success-rate decline raises a warning.

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 01e15490f0)

Solutions

  1. Pass a non-negative finite number (fraction in 0..1, e.g. 0.1 for 10%).
  2. Omit options.warnThreshold to use the built-in default of 0.1.
  3. If reading from config/env, coerce and bounds-check before calling.

Example fix

// before
collectSkillHealth({ warnThreshold: process.env.WARN_THRESHOLD });
// env unset -> '' -> Number('') === 0, ok; but 'high' -> NaN -> throws

// after
const wt = Number(process.env.WARN_THRESHOLD || 0.1);
collectSkillHealth({ warnThreshold: Number.isFinite(wt) && wt >= 0 ? wt : 0.1 });
Defensive patterns

Strategy: validation

Validate before calling

function resolveWarnThreshold(raw) {
  const n = typeof raw === 'number' ? raw : Number(raw);
  if (!Number.isFinite(n) || n < 0) return 0.1; // safe default
  return n;
}
health.collectSkillHealth({ ...opts, warnThreshold: resolveWarnThreshold(opts.warnThreshold) });

Type guard

function isValidWarnThreshold(v) {
  const n = Number(v);
  return Number.isFinite(n) && n >= 0;
}

Try / catch

try {
  collectSkillHealth({ warnThreshold });
} catch (err) {
  if (/Invalid warn threshold/.test(err.message)) collectSkillHealth({ ...opts });
  else throw err;
}

Prevention

When it happens

Trigger: Calling collectSkillHealth({ warnThreshold: 'abc' }) (Number('abc') = NaN), { warnThreshold: -0.2 }, or { warnThreshold: Infinity }. A numeric string like '0.1' is accepted because Number('0.1') = 0.1.

Common situations: Config typo passing a non-numeric; passing a negative value by mistake; confusing the threshold as a percentage (passing 10 for '10%') — that won't throw but yields wrong warnings; env var defaulting to an empty/non-numeric string.

Related errors


AI-assisted analysis of affaan-m/ECC@01e15490f0 (2026-08-13). Data as JSON: /api/errors/239c07da63f8414f. Report an issue: GitHub.