{"record":{"id":"62adc2d2df419a38","repo":"Hmbown/CodeWhale","slug":"duration-s-resolved-to-zero-seconds","errorCode":null,"errorMessage":"duration {s:?} resolved to zero seconds","messagePattern":"duration (.+?) resolved to zero seconds","errorType":"validation","errorClass":"anyhow::Error","httpStatus":null,"severity":"error","filePath":"crates/cli/src/metrics.rs","lineNumber":102,"sourceCode":"                    'h' => 3_600,\n                    'm' => 60,\n                    's' => 1,\n                    _ => unreachable!(),\n                };\n                total += n * factor;\n            }\n            _ => anyhow::bail!(\"unrecognised character {ch:?} in duration {s:?}\"),\n        }\n    }\n\n    if !num_buf.is_empty() {\n        // Trailing bare number — treat as seconds.\n        let n: i64 = num_buf.parse()?;\n        total += n;\n    }\n\n    if total == 0 {\n        anyhow::bail!(\"duration {s:?} resolved to zero seconds\");\n    }\n\n    Ok(total)\n}\n\n// ──────────────────────────────────────────────────────────────────────────────\n// Rollup data model\n// ──────────────────────────────────────────────────────────────────────────────\n\n/// Per-tool aggregated counters.\n#[derive(Debug, Default, serde::Serialize)]\npub struct ToolStats {\n    pub calls: u64,\n    /// Calls that were auto-approved (no prompt required).\n    pub auto_approved: u64,\n    /// Calls that required a manual prompt.\n    pub prompted: u64,\n    /// Total elapsed ms (from events that carry this field).","sourceCodeStart":84,"sourceCodeEnd":120,"githubUrl":"https://github.com/Hmbown/CodeWhale/blob/8880682c63083a91624de936797efa3ce9e498fd/crates/cli/src/metrics.rs#L84-L120","documentation":"The duration string parsed successfully but every component summed to zero seconds, which parse_duration_secs rejects because a zero-length window is meaningless for the metrics cutoff computation. A trailing bare number is treated as seconds, so \"0\" also lands here.","triggerScenarios":"Passing \"0s\", \"0\", \"00m00s\", or a --since flag whose variable expanded to a bare zero.","commonSituations":"Scripts intending \"all history\" by passing 0 — not supported; empty-variable defaults coercing to \"0\"; computed windows rounding down to zero.","solutions":["Choose an explicit non-zero window, e.g. \"1d\" or \"30d\"","Guard scripted variables so an empty/unset value becomes a sane default instead of \"0\"","Compute windows upstream and pass a concrete non-zero duration"],"exampleFix":"# before\n codewhale metrics --since \"${SINCE:-0}\"   # error: duration \"0\" resolved to zero seconds\n\n# after\n codewhale metrics --since \"${SINCE:-7d}\"","handlingStrategy":"validation","validationCode":"// Reject zero-valued windows in your wrapper before invoking the CLI:\nfn nonzero_duration_secs(s: &str) -> Option<i64> {\n    let total = parse_duration_secs(s).ok()?;\n    (total > 0).then_some(total)\n}","typeGuard":null,"tryCatchPattern":null,"preventionTips":["Default scripted windows to a real value (e.g. 7d), never 0","Guard against empty environment variables coercing to \"0\"","Decide explicitly what \"all history\" means and pass a sufficiently large window like \"365d\""],"tags":["cli","metrics","duration","validation","rust"],"backgroundTag":null,"analyzedSha":"8880682c63083a91624de936797efa3ce9e498fd","analyzedAt":"2026-08-16T11:31:27.956Z","schemaVersion":2},"datasetVersion":"2026-08-16T13:17:31.715Z"}