{"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":null,"httpStatus":null,"severity":"error","filePath":"crates/cli/src/metrics.rs","lineNumber":131,"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":113,"sourceCodeEnd":149,"githubUrl":"https://github.com/Hmbown/CodeWhale/blob/73e0f67d83c59909b571efdfc88c4bc28c309cb1/crates/cli/src/metrics.rs#L113-L149","documentation":"parse_duration_secs parses human duration strings like '2h30m' into a total number of seconds. It throws this error when the parsed components sum to zero (e.g. '0m', '0', or an empty numeric input), because a zero-length duration is meaningless for the caller (parse_since computes a 'since' timestamp from it) and would otherwise silently produce a full-history or degenerate query.","triggerScenarios":"Calling parse_since with a duration string whose numeric components total 0, e.g. `--since 0`, `--since 0s`, `--since 0h`, or input where the parsing path ends with total == 0.","commonSituations":"Scripting the metrics CLI with a variable that defaults to '0' or '0m' instead of a real duration; copy-pasting an example with a placeholder 0 value; forgetting the unit multiplier logic and assuming '0h' means 'now'.","solutions":["Pass a non-zero duration value, e.g. `--since 24h`, `--since 30m`, `--since 7d`.","Check the shell variable feeding --since; a default of 0/0s must be replaced with a meaningful default.","If you genuinely want 'everything since epoch', pass an explicit absolute timestamp instead of a zero duration, if supported by parse_since."],"exampleFix":"// before\ncodewhale-metrics --since $WINDOW   # WINDOW=\"0h\"\n// after\ncodewhale-metrics --since ${WINDOW:-24h}  # guard against empty/zero default","handlingStrategy":"validation","validationCode":"let s = duration_str.trim();\nif s.is_empty() || s == \"0\" || s == \"0s\" || s == \"0m\" || s == \"0h\" {\n    return Err(anyhow::anyhow!(\"--since needs a non-zero duration, e.g. 24h\"));\n}","typeGuard":"fn is_nonzero_duration(s: &str) -> bool {\n    parse_duration_secs(s).map(|v| v > 0).unwrap_or(false)\n}","tryCatchPattern":null,"preventionTips":["Give shell variables feeding --since a non-zero default (e.g. ${WINDOW:-24h}).","Never pass a bare 0 or unit-less 0 to duration arguments.","Validate user-supplied durations in scripts before invoking the CLI."],"tags":["cli","duration","argument-validation"],"backgroundTag":"invalid-duration-format","analyzedSha":"73e0f67d83c59909b571efdfc88c4bc28c309cb1","analyzedAt":"2026-09-22T01:30:00.501Z","contentChangedAt":"2026-09-22T01:30:00.501Z","schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}