zeroclaw-labs/zeroclaw · error · anyhow::Error

effective cost limit must be a non-negative finite value

Error message

effective cost limit must be a non-negative finite value

What it means

cost_limit_to_db validates an effective cost limit before it is persisted to the goal extension row: the f64 must be finite and >= 0. NaN, +/-infinity, and negatives are rejected (pinned by update_goal_limits_rejects_invalid_effective_limits). This keeps unparseable or nonsensical values out of the SQLite column where they would silently corrupt later budget enforcement.

Source

Thrown at crates/zeroclaw-runtime/src/control_plane/task_store_sqlite/goal.rs:232

}

fn token_limit_from_db(value: Option<i64>) -> rusqlite::Result<Option<u64>> {
    value
        .map(|value| {
            u64::try_from(value).map_err(|e| {
                rusqlite::Error::FromSqlConversionFailure(
                    0,
                    rusqlite::types::Type::Integer,
                    e.into(),
                )
            })
        })
        .transpose()
}

fn cost_limit_to_db(value: f64) -> Result<f64> {
    if !value.is_finite() || value < 0.0 {
        anyhow::bail!("effective cost limit must be a non-negative finite value");
    }
    Ok(value)
}

fn cost_limit_from_db(value: Option<f64>) -> rusqlite::Result<Option<f64>> {
    match value {
        Some(value) if !value.is_finite() || value < 0.0 => {
            Err(rusqlite::Error::FromSqlConversionFailure(
                0,
                rusqlite::types::Type::Real,
                format!("invalid effective cost limit {value}").into(),
            ))
        }
        other => Ok(other),
    }
}

fn goal_limits_to_db(

View on GitHub (pinned to 88bb9c8533)

Solutions

  1. Trace the origin of the f64 and fix the computation (guard divisions, validate parsed config)
  2. Clamp or reject at your API boundary before calling update_goal_limits
  3. Encode 'no limit' as None (omitted field), never as infinity
  4. Add a unit test at your config layer asserting NaN/negative rejection

Example fix

// before
let limit = budget_total / task_count; // 0 tasks => NaN/inf
store.update_goal_limits(id, tokens, Some(limit)).await?;

// after
let limit = budget_total / task_count.max(1.0);
if !limit.is_finite() || limit < 0.0 { anyhow::bail!("invalid cost limit"); }
store.update_goal_limits(id, tokens, Some(limit)).await?;
Defensive patterns

Strategy: validation

Validate before calling

let limit = computed_limit();
if !valid_cost_limit(limit) {
    anyhow::bail!("cost limit must be a non-negative finite number, got {limit}");
}
store.update_goal_limits(id, tokens, Some(limit)).await?;

Type guard

fn valid_cost_limit(v: f64) -> bool {
    v.is_finite() && v >= 0.0
}

Try / catch

if let Err(ref e) = store.update_goal_limits(id, t, Some(l)).await {
    if e.to_string().contains("non-negative finite") {
        // the computed limit is NaN/inf/negative: fix the upstream math, not the store
    }
}

Prevention

When it happens

Trigger: update_goal_limits called with a computed float such as 0.0/0.0 (NaN), division by zero yielding infinity, or a negative result from subtraction/parse errors; values read from user config without prior validation.

Common situations: Config math producing NaN (unset field defaulting to 0 then divided); 'unlimited' encoded as f64::INFINITY; currency adjustments going negative; JSON null deserialized as 0.0 and then mis-scaled.

Related errors


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