zeroclaw-labs/zeroclaw · error

Token usage cost must be a finite, non-negative value

Error message

Token usage cost must be a finite, non-negative value

What it means

The internal recorder behind record_usage_with_owned_task_attribution and record_scoped_usage_with_owned_task_attribution validates that usage.cost_usd is finite and non-negative before writing it into aggregated storage. This mirrors the check_budget guard: a single NaN or negative cost would corrupt daily/monthly aggregates and every later limit decision. Rejections are logged as WARN Reject events with the cost_usd attribute attached.

Source

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

        honor_enabled: bool,
    ) -> Result<()> {
        let (enabled, track_per_agent) = {
            let config = self.config.read();
            (config.enabled, config.track_per_agent)
        };
        if honor_enabled && !enabled {
            return Ok(());
        }

        if !usage.cost_usd.is_finite() || usage.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!({"cost_usd": usage.cost_usd})),
                "token usage record rejected: cost is not finite or is negative"
            );
            anyhow::bail!("Token usage cost must be a finite, non-negative value");
        }

        let effective_alias = if track_per_agent {
            agent_alias.map(str::to_string)
        } else {
            None
        };
        let cost_usd = usage.cost_usd;
        let total_tokens = usage.total_tokens;
        let record =
            CostRecord::with_attribution(&self.session_id, effective_alias.clone(), task_id, usage);

        {
            let mut storage = self.lock_storage();
            storage.add_record(record)?;
        }

        {

View on GitHub (pinned to 88bb9c8533)

Solutions

  1. Fix the upstream cost computation so cost_usd is always a finite >= 0.0 value
  2. Treat a missing price as 0.0 and log it, rather than propagating NaN into the record
  3. Sanitize the record before submission: `let cost = if usage.cost_usd.is_finite() && usage.cost_usd >= 0.0 { usage.cost_usd } else { 0.0 };`
  4. Add tests over the pricing table for every model name you actually route to

Example fix

// before
tracker.record_usage_with_owned_task_attribution(usage, task_id).await?;

// after
let mut usage = usage;
if !usage.cost_usd.is_finite() || usage.cost_usd < 0.0 {
    usage.cost_usd = 0.0;
}
tracker.record_usage_with_owned_task_attribution(usage, task_id).await?;
Defensive patterns

Strategy: validation

Validate before calling

let mut record = record;
if !record.cost_usd.is_finite() || record.cost_usd < 0.0 {
    tracing::warn!(cost = record.cost_usd, "invalid cost; recording as 0.0");
    record.cost_usd = 0.0;
}
tracker.record_usage_with_owned_task_attribution(record, task_id).await?;

Type guard

fn has_valid_cost(u: &UsageRecord) -> bool {
    u.cost_usd.is_finite() && u.cost_usd >= 0.0
}

Try / catch

if let Err(e) = tracker.record_usage_with_owned_task_attribution(record.clone(), task_id).await {
    if !record.cost_usd.is_finite() || record.cost_usd < 0.0 {
        tracing::error!(error = %e, "usage cost invalid; dropping record to protect aggregates");
        return Ok(());
    }
    return Err(e);
}

Prevention

When it happens

Trigger: Calling record_usage_with_owned_task_attribution (or the scoped variant) with a UsageRecord whose cost_usd field is NaN, infinite, or negative — typically because the cost was computed from a missing price entry or a bad parse upstream.

Common situations: Provider pricing lookups that return Option::None and get unwisely mapped to NaN; per-token cost arithmetic hitting infinity on zero-division; discount/refund logic subtracting past zero before recording usage.

Related errors


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