Hmbown/CodeWhale · error · anyhow::Error
invalid duration component: {num_buf:?}
Error message
invalid duration component: {num_buf:?} What it means
parse_duration_secs walks the string as runs of digits terminated by unit letters d/h/m/s. When a unit letter arrives but the digit buffer is empty (or the accumulated number fails i64 parsing, i.e. overflow), this error names the offending buffer. Notably there is no millisecond unit: "100ms" fails here because the trailing s follows an empty buffer.
Source
Thrown at crates/cli/src/metrics.rs:80
pub fn parse_since(s: &str) -> Result<DateTime<Utc>> {
let s = s.trim().to_ascii_lowercase();
let s = s.strip_prefix("now-").unwrap_or(&s);
let secs = parse_duration_secs(s)?;
Ok(Utc::now() - Duration::seconds(secs))
}
fn parse_duration_secs(s: &str) -> Result<i64> {
// Walk through the string accumulating numbers and consuming unit suffixes.
let mut total: i64 = 0;
let mut num_buf = String::new();
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;View on GitHub (pinned to 8880682c63)
Solutions
- Express the duration with supported units only: d/h/m/s (e.g. "100ms" -> "0s" is invalid, use "1s" or drop it)
- Ensure every unit letter is preceded by digits — no stray or doubled units
- Keep each component under i64 range; split into multiple components instead
- Use the accepted "now-2h" prefix form where the flag documents it
Example fix
# before codewhale metrics --since 100ms # error: invalid duration component: "" # after codewhale metrics --since 1s
Defensive patterns
Strategy: validation
Validate before calling
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.ends_with(|c: char| matches!(c, 'd' | 'h' | 'm' | 's')) == false
} Prevention
- Remember there is no ms unit — convert milliseconds to seconds before passing
- Always precede a unit letter with digits; reject strings like "ms" or lone "h" in wrappers
- Prefer simple single-unit forms ("7d", "24h", "90m") in scripts
When it happens
Trigger: Passing "100ms" or "1500ms" to a --since-style flag; a bare unit like "d" or "h" with no number; a huge component like "99999999999999999999s" overflowing i64.
Common situations: Assuming humantime/ms support; copy-pasting latency-style durations into a retention window flag; scripting with empty variables producing a lone unit.
Related errors
- unrecognised character {ch:?} in duration {s:?}
- duration {s:?} resolved to zero seconds
- invalid JSON payload at key {MCP_SERVER_DEFINITIONS_KEY}; co
- could not parse match block after {signature!r}
- No local {} API key was found in config, the secret store, o
AI-assisted analysis of Hmbown/CodeWhale@8880682c63 (2026-08-16).
Data as JSON: /api/errors/23a5bf9a3882af6c.
Report an issue: GitHub.