Hmbown/CodeWhale · error
unrecognised character
Error message
unrecognised character {ch:?} in duration {s:?} What it means
`parse_duration_secs` (used by `parse_since` for --since values) parses human durations like 90s/5m/2h via a character-driven state machine. If it encounters a character that is not a digit, a recognized unit letter (s/m/h...), or another accepted token, it bails naming the offending character and the full input string.
Solutions
- Use bare seconds or single-letter units: `--since 90`, `--since 90s`, `--since 5m`, `--since 2h`.
- Remove unsupported characters (spaces, commas, full words) from the duration string.
- If the value comes from a variable/script, sanitize/validate it with a regex like ^[0-9]+[smh]?$ before invoking.
Example fix
// before cli --since 5min // after cli --since 5m
Defensive patterns
Strategy: validation
Validate before calling
let re = std::regex::Regex::new(r"^[0-9]+[smh]?$").unwrap();
if !re.is_match(since) {
eprintln!("invalid --since '{since}'; use forms like 90, 90s, 5m, 2h");
std::process::exit(2);
} Try / catch
match parse_since(raw) {
Err(e) if e.to_string().contains("unrecognised character") => {
eprintln!("{e}; supported units are single letters s/m/h");
}
Err(e) => return Err(e),
Ok(secs) => use_since(secs),
} Prevention
- Use single-letter units (s/m/h) or bare seconds, never min/hr/secs words.
- Validate --since inputs with a regex in scripts before invoking the CLI.
- Watch for copy-paste artifacts (spaces, commas, unicode dashes) in durations.
When it happens
Trigger: Passing a --since duration containing an unsupported character, e.g. `--since 5min`, `--since 1h30`, `--since 90S` (uppercase unit, since matching is on exact lowercase per the match arms), or stray punctuation.
Common situations: Typing full unit words (min/hr/secs) instead of single letters; locale or copy-paste artifacts like commas or unicode minus; assuming case-insensitive units.
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
- invalid duration component
- duration resolved to zero seconds
- invalid header pair ' ', expected name=value
- Invalid input
- unknown argument
AI-assisted analysis of Hmbown/CodeWhale@73e0f67d83 (2026-09-22).
Data as JSON: /api/errors/20a5414ee0b798c7.
Report an issue: GitHub.
Appendix: source
Thrown at crates/cli/src/metrics.rs:120
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 modelView on GitHub (pinned to 73e0f67d83)