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

  1. Validate before calling: `if (!Number.isFinite(actualUsd) || actualUsd < 0) throw ...` or clamp to the reserved estimate
  2. If actual cost is unknown at commit time, commit the reserved estimatedUsd as the actual (the API accepts over/under-runs against the reservation)
  3. 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

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


AI-assisted analysis of ruvnet/ruflo@6b01dc5a68 (2026-08-12). Data as JSON: /api/errors/ef947fa4728867f1. Report an issue: GitHub.