ruvnet/ruflo · error · Error
actualUsd must be a non-negative finite number
Error message
actualUsd must be a non-negative finite number
What it means
Input guard at the top of AtomicBbsRoomBudgetTracker.commit(), symmetric to the reserve() guard (error 163). Validates actualUsd — the real cost charged against a previously reserved amount — before opening the BEGIN IMMEDIATE transaction. Uses Number.isFinite() and a < 0 test. Rejecting here prevents a malformed actual cost from corrupting committed totals or violating the SQL CHECK on actual_usd.
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 6b01dc5a68)
Solutions
- Validate before calling: `if (!Number.isFinite(actualUsd) || actualUsd < 0) throw ...` or clamp to the reserved estimate
- If actual cost is unknown at commit time, commit the reserved estimatedUsd as the actual (the API accepts over/under-runs against the reservation)
- Filter NaN out of any summation feeding actualUsd before it reaches commit()
Example fix
// before: const actual = sumUsage(usageRows); // NaN if any row has null cost tracker.commit(reservationId, actual); // throws // after: const actual = usageRows.reduce((a, r) => a + (Number.isFinite(r.cost) ? r.cost : 0), 0); tracker.commit(reservationId, Math.max(0, actual));
Defensive patterns
Strategy: validation
Validate before calling
function safeActualCost(actualUsd: unknown): number {
const n = typeof actualUsd === 'number' ? actualUsd : Number(actualUsd);
if (!Number.isFinite(n) || n < 0) return 0;
return n;
}
// before calling commit():
const actual = safeActualCost(reportedCost);
tracker.commit(reservationId, actual); Type guard
function isValidActualCost(v: unknown): v is number {
return typeof v === 'number' && Number.isFinite(v) && v >= 0;
} Prevention
- Filter NaN out of any summation that feeds actualUsd before calling commit()
- If actual cost is unavailable at commit time, commit the reserved estimatedUsd as a safe fallback
- Guard external billing-API responses against null/undefined before coercion
When it happens
Trigger: Calling tracker.commit(reservationId, actualUsd) where actualUsd is NaN, ±Infinity, or negative. This runs after a reservation was already created by reserve() and the actual spend is being finalized.
Common situations: Actual cost reported by an external billing API as null/undefined then coerced to NaN; a usage meter returning -1 on error; a division that yields Infinity; a cost-aggregation sum that includes a NaN term poisoning the total.
Related errors
- estimatedUsd must be a non-negative finite number
- selectAgentBackend: pod.budgetUsdMonthly must be a finite nu
- pod-template at ${path}: field "${key}" must be a finite num
- pod-template at ${path}: bench.scheduleHours must be ≥1
- pod-template at ${path}: auditReadView.retentionDays must be
AI-assisted analysis of ruvnet/ruflo@6b01dc5a68 (2026-08-12).
Data as JSON: /api/errors/ef947fa4728867f1.
Report an issue: GitHub.