ruvnet/ruflo · error
actualUsd must be a non-negative finite number
Error message
actualUsd must be a non-negative finite number
What it means
BBS budget tracker commit() rejects its actualUsd with the same numeric contract as reserve(): finite and non-negative, validated before the transaction opens. The guard protects the commit path (ADR-164.1 §5.3) which charges the actual cost against the monthly budget and may transition an expired reservation to 'committed_post_expiry'.
Solutions
- Sanitize the metered amount before committing: const actual = Number.isFinite(x) && x >= 0 ? x : 0 (or drop the event and alert)
- Handle refunds/credits through a dedicated accounting path instead of negative commit() values
- Trace NaN sources in usage aggregation (undefined * number, parseFloat of malformed strings)
- Add a unit test asserting commit rejects NaN, Infinity, and -1 so regressions surface early
Example fix
// before
tracker.commit(reservationId, usage.actualUsd /* NaN when usage row missing */);
// after
const actual = Number(usage.actualUsd);
if (!Number.isFinite(actual) || actual < 0) throw new TypeError(`bad metered cost: ${usage.actualUsd}`);
tracker.commit(reservationId, actual); Defensive patterns
Strategy: validation
Validate before calling
const actual = Number(meteredEvent.actualUsd);
if (!Number.isFinite(actual) || actual < 0) {
deadLetter(meteredEvent); // never feed bad metering into commit()
} else {
tracker.commit(reservationId, actual);
} Type guard
function isCommitUsd(v: unknown): v is number {
return typeof v === 'number' && Number.isFinite(v) && v >= 0;
} Prevention
- Sanitize metered values at ingestion; dead-letter malformed events instead of passing them on
- Route refunds/credits through a dedicated path — commit() is not a negative-amount API
- Assert Number.isFinite on all division results in usage aggregation
When it happens
Trigger: Calling tracker.commit(reservationId, actualUsd) with NaN/±Infinity/negative actualUsd — e.g. usage math that produced NaN, an API returning a negative refund/adjustment that was passed through, or a string amount from a metering event.
Common situations: Metering pipelines forwarding raw provider numbers without sanitization; negative adjustment/refund events that should go through a different path, not commit(); JSON deserialization yielding strings for numeric fields.
Related errors
- estimatedUsd must be a non-negative finite number
- agent must be an object
- bench must be an object
- bench.successCriteria must have ≥1 entry
- field " " must be a boolean
AI-assisted analysis of ruvnet/ruflo@fa13ee4ad6 (2026-08-18).
Data as JSON: /api/errors/ef947fa4728867f1.
Report an issue: GitHub.
Appendix: source
Thrown at v3/@claude-flow/cli/src/business-pods/bbs-budget-tracker.ts:299
return { ok: true, reservationId, remainingAfterReserve: remaining };
} catch (err) {
if (transactionOpen) {
try { rollbackStmt.run(); } catch { /* already-rolled */ }
}
throw err;
}
}
/**
* Commit the reservation with the actual cost. Late commits (expired
* before commit landed) ARE accepted, transitioned to
* 'committed_post_expiry', charged to the budget, and surfaced via
* `warned: 'COMMIT_AFTER_EXPIRY'` plus a `reservation.committed_post_expiry`
* audit emit. See ADR-164.1 §5.3 + §8.1.
*/
commit(reservationId: string, actualUsd: number): CommitResult {
if (!Number.isFinite(actualUsd) || actualUsd < 0) {
throw new Error('actualUsd must be a non-negative finite number');
}
const nowMs = this.clock();
const beginStmt = this.db.prepare('BEGIN IMMEDIATE');
const commitStmt = this.db.prepare('COMMIT');
const rollbackStmt = this.db.prepare('ROLLBACK');
beginStmt.run();
let transactionOpen = true;
try {
const row = this.db
.prepare(
`SELECT state, room_id, estimated_usd, reserved_at, expires_at
FROM bbs_budget_reservations
WHERE reservation_id = ?`,
)
.get(reservationId) as
| { state: string; room_id: string; estimated_usd: number; reserved_at: number; expires_at: number }View on GitHub (pinned to fa13ee4ad6)