Hmbown/CodeWhale · error

duration resolved to zero seconds

Error message

duration {s:?} resolved to zero seconds

What it means

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.

Solutions

  1. Pass a non-zero duration value, e.g. `--since 24h`, `--since 30m`, `--since 7d`.
  2. Check the shell variable feeding --since; a default of 0/0s must be replaced with a meaningful default.
  3. If you genuinely want 'everything since epoch', pass an explicit absolute timestamp instead of a zero duration, if supported by parse_since.

Example fix

// before
codewhale-metrics --since $WINDOW   # WINDOW="0h"
// after
codewhale-metrics --since ${WINDOW:-24h}  # guard against empty/zero default
Defensive patterns

Strategy: validation

Validate before calling

let s = duration_str.trim();
if s.is_empty() || s == "0" || s == "0s" || s == "0m" || s == "0h" {
    return Err(anyhow::anyhow!("--since needs a non-zero duration, e.g. 24h"));
}

Type guard

fn is_nonzero_duration(s: &str) -> bool {
    parse_duration_secs(s).map(|v| v > 0).unwrap_or(false)
}

Prevention

When it happens

Trigger: 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.

Common situations: 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'.

Understand the failure class

Background: "invalid duration" / "failed to parse duration": why your timeout, interval, or TTL string is rejected and which formats each library accepts — this error's family across 32 libraries.

Related errors


AI-assisted analysis of Hmbown/CodeWhale@73e0f67d83 (2026-09-22). Data as JSON: /api/errors/62adc2d2df419a38. Report an issue: GitHub.

Appendix: source

Thrown at crates/cli/src/metrics.rs:131

                    'h' => 3_600,
                    'm' => 60,
                    's' => 1,
                    _ => unreachable!(),
                };
                total += n * factor;
            }
            _ => anyhow::bail!("unrecognised character {ch:?} in duration {s:?}"),
        }
    }

    if !num_buf.is_empty() {
        // Trailing bare number — treat as seconds.
        let n: i64 = num_buf.parse()?;
        total += n;
    }

    if total == 0 {
        anyhow::bail!("duration {s:?} resolved to zero seconds");
    }

    Ok(total)
}

// ──────────────────────────────────────────────────────────────────────────────
// Rollup data model
// ──────────────────────────────────────────────────────────────────────────────

/// Per-tool aggregated counters.
#[derive(Debug, Default, serde::Serialize)]
pub struct ToolStats {
    pub calls: u64,
    /// Calls that were auto-approved (no prompt required).
    pub auto_approved: u64,
    /// Calls that required a manual prompt.
    pub prompted: u64,
    /// Total elapsed ms (from events that carry this field).

View on GitHub (pinned to 73e0f67d83)