Hmbown/CodeWhale · error · anyhow::Error

unrecognised character {ch:?} in duration {s:?}

Error message

unrecognised character {ch:?} in duration {s:?}

What it means

Any character other than ASCII digits and the unit letters d/h/m/s aborts duration parsing: spaces, '.', uppercase letters (parse_since lowercases first, but direct calls do not), and unsupported units like w, y, x. The error echoes the offending character and the full input.

Source

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

    for ch in s.chars() {
        match ch {
            '0'..='9' => num_buf.push(ch),
            'd' | 'h' | 'm' | 's' => {
                let n: i64 = num_buf
                    .parse()
                    .map_err(|_| anyhow::anyhow!("invalid duration component: {num_buf:?}"))?;
                num_buf.clear();
                let factor = match ch {
                    'd' => 86_400,
                    '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

View on GitHub (pinned to 8880682c63)

Solutions

  1. Rewrite in the supported grammar: "1h30m", "90m", "5400s" — no spaces or decimals
  2. Convert decimals to smaller units ("1.5h" -> "90m")
  3. Lowercase the input; uppercase units are not accepted by the raw parser

Example fix

# before
 codewhale metrics --since "1h 30m"   # error: unrecognised character ' '

# after
 codewhale metrics --since 1h30m
Defensive patterns

Strategy: validation

Validate before calling

// Rust: validate against the accepted grammar before calling the CLI/parser:
fn valid_duration(s: &str) -> bool {
    let s = s.trim().to_ascii_lowercase();
    let s = s.strip_prefix("now-").unwrap_or(&s);
    !s.is_empty()
        && s.chars().all(|c| c.is_ascii_digit() || matches!(c, 'd' | 'h' | 'm' | 's'))
        && s.starts_with(|c: char| c.is_ascii_digit())
}

Prevention

When it happens

Trigger: Passing "1.5h", "2 w", "1H" via a direct parse call, "5min", "1h,30m", or ISO-8601 "PT2H" style strings to a --since flag.

Common situations: Copy-pasting humantime strings with spaces ("1h 30m"); decimal hours; expecting week/year or min/ms units; locale-formatted numbers.

Related errors


AI-assisted analysis of Hmbown/CodeWhale@8880682c63 (2026-08-16). Data as JSON: /api/errors/20a5414ee0b798c7. Report an issue: GitHub.