JuliusBrussee/caveman · error

cannot render ${text} as a stable numeric literal

Error message

cannot render ${text} as a stable numeric literal

What it means

Thrown by number() in scripts/generate-agent-catalog.mjs when a price that already passed rate() validation still renders as a string that does not match the canonical numeric-literal regex (no leading zeros, no '+', no capital 'E', no negative sign). This is a defensive invariant guarding deterministic output: the generated catalog.ts must be byte-stable so the drift test can recompute it. In practice it is nearly unreachable because rate() already rejected negatives and non-finite values.

Source

Thrown at scripts/generate-agent-catalog.mjs:216

      }
      return { key, reason: reasons.join("; ") };
    })
    .sort((left, right) => (left.key < right.key ? -1 : left.key > right.key ? 1 : 0));
  return { selected, skipped };
}

function rate(value, field, label) {
  if (value === undefined || value === null) return null;
  if (typeof value !== "number" || !Number.isFinite(value) || value < 0) {
    throw new Error(`${label}: ${field} must be a finite non-negative number`);
  }
  return value;
}

function number(value) {
  const text = String(value);
  if (!/^(0|[1-9][0-9]*)(\.[0-9]+)?(e[+-]?[0-9]+)?$/.test(text)) {
    throw new Error(`cannot render ${text} as a stable numeric literal`);
  }
  return text;
}

/** Renders the full public/agent/src/catalog.ts module text. */
export function renderCatalogModule(catalogBytes, label = CATALOG_LABEL) {
  const digest = createHash("sha256").update(catalogBytes).digest("hex");
  const { selected, skipped } = selectRows(parseCatalogYaml(catalogBytes.toString("utf8"), label), label);
  if (selected.length === 0) throw new Error(`${label}: no priced region-agnostic rows found`);
  const entries = selected.map(({ key, price }) => [
    `  ${JSON.stringify(key)}: Object.freeze({`,
    `    inputPerMillion: ${number(price.inputPerMillion)},`,
    `    outputPerMillion: ${number(price.outputPerMillion)},`,
    `    cacheReadPerMillion: ${number(price.cacheReadPerMillion)},`,
    `    cacheWritePerMillion: ${number(price.cacheWritePerMillion)},`,
    `    reasoningPerMillion: ${number(price.reasoningPerMillion)},`,
    `  }),`,
  ].join("\n")).join("\n");

View on GitHub (pinned to 27d5a3981a)

Solutions

  1. If hit while editing the catalog, double-check the named price value is a plain decimal (e.g. 1.5, 0.25) and re-run.
  2. If it reproduces with valid input, file a bug against scripts/generate-agent-catalog.mjs — rate() and number() have disagreeing notions of a valid number.
Defensive patterns

Strategy: try-catch

Try / catch

try {
  renderCatalogModule(bytes);
} catch (error) {
  if (error.message.startsWith("cannot render")) throw new BugError("generate-agent-catalog invariant broken", { cause: error });
  throw error;
}

Prevention

When it happens

Trigger: Only reachable through an internal inconsistency — e.g. a value whose String() form is non-canonical (negative zero rendering, exotic exponent forms) or a bug/hand-edit that bypasses rate(). Normal YAML input cannot produce it.

Common situations: Essentially none for catalog editors; would indicate a bug in the script itself or a hand-modified selected row in a test harness.

Related errors


AI-assisted analysis of JuliusBrussee/caveman@27d5a3981a (2026-08-15). Data as JSON: /api/errors/f3e3720ec790c5d9. Report an issue: GitHub.