{"record":{"id":"88540ff61e0948ff","repo":"zeroclaw-labs/zeroclaw","slug":"effective-cost-limit-must-be-a-non-negative-finite","errorCode":null,"errorMessage":"effective cost limit must be a non-negative finite value","messagePattern":"effective cost limit must be a non-negative finite value","errorType":"exception","errorClass":"anyhow::Error","httpStatus":null,"severity":"error","filePath":"crates/zeroclaw-runtime/src/control_plane/task_store_sqlite/goal.rs","lineNumber":232,"sourceCode":"}\n\nfn token_limit_from_db(value: Option<i64>) -> rusqlite::Result<Option<u64>> {\n    value\n        .map(|value| {\n            u64::try_from(value).map_err(|e| {\n                rusqlite::Error::FromSqlConversionFailure(\n                    0,\n                    rusqlite::types::Type::Integer,\n                    e.into(),\n                )\n            })\n        })\n        .transpose()\n}\n\nfn cost_limit_to_db(value: f64) -> Result<f64> {\n    if !value.is_finite() || value < 0.0 {\n        anyhow::bail!(\"effective cost limit must be a non-negative finite value\");\n    }\n    Ok(value)\n}\n\nfn cost_limit_from_db(value: Option<f64>) -> rusqlite::Result<Option<f64>> {\n    match value {\n        Some(value) if !value.is_finite() || value < 0.0 => {\n            Err(rusqlite::Error::FromSqlConversionFailure(\n                0,\n                rusqlite::types::Type::Real,\n                format!(\"invalid effective cost limit {value}\").into(),\n            ))\n        }\n        other => Ok(other),\n    }\n}\n\nfn goal_limits_to_db(","sourceCodeStart":214,"sourceCodeEnd":250,"githubUrl":"https://github.com/zeroclaw-labs/zeroclaw/blob/88bb9c8533fc57ed7a03e36ca7c9ed2bf8336dcc/crates/zeroclaw-runtime/src/control_plane/task_store_sqlite/goal.rs#L214-L250","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","solutions":["Trace the origin of the f64 and fix the computation (guard divisions, validate parsed config)","Clamp or reject at your API boundary before calling update_goal_limits","Encode 'no limit' as None (omitted field), never as infinity","Add a unit test at your config layer asserting NaN/negative rejection"],"exampleFix":"// before\nlet limit = budget_total / task_count; // 0 tasks => NaN/inf\nstore.update_goal_limits(id, tokens, Some(limit)).await?;\n\n// after\nlet limit = budget_total / task_count.max(1.0);\nif !limit.is_finite() || limit < 0.0 { anyhow::bail!(\"invalid cost limit\"); }\nstore.update_goal_limits(id, tokens, Some(limit)).await?;","handlingStrategy":"validation","validationCode":"let limit = computed_limit();\nif !valid_cost_limit(limit) {\n    anyhow::bail!(\"cost limit must be a non-negative finite number, got {limit}\");\n}\nstore.update_goal_limits(id, tokens, Some(limit)).await?;","typeGuard":"fn valid_cost_limit(v: f64) -> bool {\n    v.is_finite() && v >= 0.0\n}","tryCatchPattern":"if let Err(ref e) = store.update_goal_limits(id, t, Some(l)).await {\n    if e.to_string().contains(\"non-negative finite\") {\n        // the computed limit is NaN/inf/negative: fix the upstream math, not the store\n    }\n}","preventionTips":["Validate numeric config at load time, not at persistence time","Represent 'unlimited' as None, never f64::INFINITY","Guard every division that feeds a limit","Unit-test config parsers with empty and unset fields"],"tags":["validation","f64","nan","infinity","goal-store","sqlite"],"backgroundTag":"invalid-numeric-config","analyzedSha":"88bb9c8533fc57ed7a03e36ca7c9ed2bf8336dcc","analyzedAt":"2026-08-23T01:07:41.857Z","schemaVersion":2},"datasetVersion":"2026-08-23T08:06:27.607Z"}