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

  1. Use bare seconds or single-letter units: `--since 90`, `--since 90s`, `--since 5m`, `--since 2h`.
  2. Remove unsupported characters (spaces, commas, full words) from the duration string.
  3. 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

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


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 model

View on GitHub (pinned to 73e0f67d83)