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
- 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.
- 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
- Never hand-edit public/agent/src/catalog.ts — regenerate it from the YAML.
- If this fires with valid input, report it: rate() and number() disagree, which is a script bug.
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
- ${label}:${lineNo}: "${text}" is not a finite number
- cave_mastra_terminal_failure
- cave_eve_usage_missing
- cave_retry_accounting_invalid
- cave_budget_max_invalid
AI-assisted analysis of JuliusBrussee/caveman@27d5a3981a (2026-08-15).
Data as JSON: /api/errors/f3e3720ec790c5d9.
Report an issue: GitHub.