zeroclaw-labs/zeroclaw · error

Estimated cost must be a finite, non-negative value

Error message

Estimated cost must be a finite, non-negative value

What it means

CostTracker::check_budget validates the per-call cost estimate before comparing it against daily and monthly spending limits, rejecting NaN, infinity, and negative values. The guard exists because one non-finite float would poison every subsequent comparison and render all budget limits meaningless. The rejection is logged as a WARN Reject event carrying the offending estimated_cost_usd attribute.

Source

Thrown at crates/zeroclaw-config/src/cost/tracker.rs:109

        self.lock_storage().path.clone()
    }

    /// Check if a request is within budget.
    pub fn check_budget(&self, estimated_cost_usd: f64) -> Result<BudgetCheck> {
        let config = self.config_snapshot();
        if !config.enabled {
            return Ok(BudgetCheck::Allowed);
        }

        if !estimated_cost_usd.is_finite() || estimated_cost_usd < 0.0 {
            ::zeroclaw_log::record!(
                WARN,
                ::zeroclaw_log::Event::new(module_path!(), ::zeroclaw_log::Action::Reject)
                    .with_outcome(::zeroclaw_log::EventOutcome::Failure)
                    .with_attrs(::serde_json::json!({"estimated_cost_usd": estimated_cost_usd})),
                "cost budget check rejected: estimated cost is not finite or is negative"
            );
            anyhow::bail!("Estimated cost must be a finite, non-negative value");
        }

        let mut storage = self.lock_storage();
        let (daily_cost, monthly_cost) = storage.get_aggregated_costs()?;

        // Check daily limit
        let projected_daily = daily_cost + estimated_cost_usd;
        if projected_daily > config.daily_limit_usd {
            return Ok(BudgetCheck::Exceeded {
                current_usd: daily_cost,
                limit_usd: config.daily_limit_usd,
                period: UsagePeriod::Day,
            });
        }

        // Check monthly limit
        let projected_monthly = monthly_cost + estimated_cost_usd;
        if projected_monthly > config.monthly_limit_usd {

View on GitHub (pinned to 88bb9c8533)

Solutions

  1. Trace the inputs: log the model price and token counts that produced the estimate before calling check_budget
  2. Default missing/unparseable prices to 0.0 instead of letting NaN propagate
  3. Guard the call site: skip or zero the estimate when `!c.is_finite() || c < 0.0`
  4. Add a unit test with the exact pricing data that produced the bad value to lock the fix in

Example fix

// before
let check = tracker.check_budget(estimated)?; // panics path on NaN

// after
let estimated = if estimated.is_finite() && estimated >= 0.0 { estimated } else { 0.0 };
let check = tracker.check_budget(estimated)?;
Defensive patterns

Strategy: validation

Validate before calling

fn sanitize_cost(c: f64) -> f64 {
    if c.is_finite() && c >= 0.0 { c } else { 0.0 }
}

let estimate = sanitize_cost(estimate);
let check = tracker.check_budget(estimate)?;

Type guard

fn is_valid_cost(c: f64) -> bool {
    c.is_finite() && c >= 0.0
}

Try / catch

match tracker.check_budget(estimate) {
    Ok(check) => Ok(check),
    Err(e) if !estimate.is_finite() || estimate < 0.0 => {
        tracing::error!(estimate, "bad cost estimate; treating as 0");
        tracker.check_budget(0.0)
    }
    Err(e) => Err(e),
}

Prevention

When it happens

Trigger: Calling `check_budget(estimated_cost_usd)` where the estimate came from math on a missing or unparsed model price (NaN propagates through arithmetic), or where a subtraction bug produced a negative number. Tests invalid_budget_estimate_is_rejected and check_tool_loop_budget pin this behavior.

Common situations: The provider pricing table has no entry for the active model so cost math yields NaN; a price string fails to parse and the error path leaks into the estimate; tool-loop estimates go negative after refunds or discount subtractions.

Related errors


AI-assisted analysis of zeroclaw-labs/zeroclaw@88bb9c8533 (2026-08-23). Data as JSON: /api/errors/ec819c1dc49aaddd. Report an issue: GitHub.