ruvnet/ruflo · error · Error
metric must be finite
Error message
metric must be finite
What it means
Thrown by the internal decimal() formatter when Number.isFinite(value) is false. decimal() converts numeric metrics (baselineScore, candidateScore, heldOutDeltas entries, lift, probabilities) into fixed-scale canonical strings for the receipt payload. NaN or Infinity cannot be represented as finite decimals and would corrupt the promotion statistics.
Source
Thrown at v3/@claude-flow/cli/src/services/flywheel-receipt.ts:207
}
export function policyCandidateId(policy: Record<string, unknown>): string {
return sha256Ref(canonicalizeJcs(policy));
}
/** UUIDv7 with a 48-bit millisecond timestamp and RFC-4122 variant bits. */
export function uuidV7(now = Date.now()): string {
const bytes = randomBytes(16);
const timestamp = BigInt(now);
for (let i = 5; i >= 0; i--) bytes[5 - i] = Number((timestamp >> BigInt(i * 8)) & 0xffn);
bytes[6] = (bytes[6] & 0x0f) | 0x70;
bytes[8] = (bytes[8] & 0x3f) | 0x80;
const hex = bytes.toString('hex');
return `${hex.slice(0, 8)}-${hex.slice(8, 12)}-${hex.slice(12, 16)}-${hex.slice(16, 20)}-${hex.slice(20)}`;
}
const decimal = (value: number, scale = 12): string => {
if (!Number.isFinite(value)) throw new Error('metric must be finite');
const normalized = value.toFixed(scale).replace(/\.?0+$/, '');
return normalized === '-0' || normalized === '' ? '0' : normalized;
};
function seedFrom(parts: string[]): { seed: number; hex: string } {
const digest = createHash('sha256').update(parts.join('')).digest();
return { seed: digest.readUInt32BE(0), hex: digest.toString('hex') };
}
/**
* Deterministic three-way quickselect. Bootstrap promotion needs one quantile,
* not a fully sorted distribution; selecting it in expected O(n) removes the
* O(n log n) sort from every evaluation. Three-way partitioning also keeps the
* common all-equal bootstrap case linear.
*/
function selectKth(values: number[], k: number): number {
let left = 0;
let right = values.length - 1;View on GitHub (pinned to 6b01dc5a68)
Solutions
- Validate all scores with Number.isFinite() before receipt creation.
- Guard upstream divisions with Math.max(Math.abs(denom), epsilon) (the code already does this for relativeLift).
- Reject or default non-finite deltas at the evaluation boundary.
- Add unit tests asserting finite-only score arrays.
Example fix
// before
createFlywheelReceipt({ baselineScore: 0.42, candidateScore: NaN, heldOutDeltas: [...], /* ... */ });
// after
if (!Number.isFinite(candidateScore)) throw new Error('candidate score not finite');
createFlywheelReceipt({ baselineScore: 0.42, candidateScore, heldOutDeltas: [...], /* ... */ }); Defensive patterns
Strategy: validation
Validate before calling
function assertAllFinite(scores: number[], label: string): void {
for (let i = 0; i < scores.length; i++) {
if (!Number.isFinite(scores[i])) {
throw new Error(`non-finite ${label}[${i}]: ${scores[i]}`);
}
}
}
assertAllFinite([baselineScore, candidateScore, ...heldOutDeltas], 'score'); Type guard
const isFiniteNumber = (x: unknown): x is number => typeof x === 'number' && Number.isFinite(x);
Try / catch
try {
createFlywheelReceipt(input);
} catch (e) {
if (e instanceof Error && e.message === 'metric must be finite') {
throw new Error('evaluation produced non-finite scores; check upstream math for NaN/Infinity');
}
throw e;
} Prevention
- Validate every score with Number.isFinite before building the receipt input.
- Guard divisions with epsilon floors (the code does this for relativeLift — replicate for your own metrics).
- Add property-based tests asserting finite-only score arrays from the evaluation pipeline.
When it happens
Trigger: Calling createFlywheelReceipt() or computePromotionStatistics() where baselineScore, candidateScore, or any element of heldOutDeltas is NaN, Infinity, or -Infinity. Also reachable if a division elsewhere produced NaN and it propagated into a score.
Common situations: Score pipeline divides by zero yielding Infinity; a missing metric defaulted to NaN; heldOutDeltas computed from malformed paired outcomes; log/softmax underflow producing NaN.
Related errors
- bootstrap iterations must be >= 100
- undefined array member at ${path}[${i}]
- undefined property at ${path}.${key}
- unsupported JSON value at ${path}
- both Ed25519 private and public PEM keys are required
AI-assisted analysis of ruvnet/ruflo@6b01dc5a68 (2026-08-12).
Data as JSON: /api/errors/e37c3ba15bbd68ce.
Report an issue: GitHub.