{"record":{"id":"20a5414ee0b798c7","repo":"Hmbown/CodeWhale","slug":"unrecognised-character-ch-in-duration-s","errorCode":null,"errorMessage":"unrecognised character {ch:?} in duration {s:?}","messagePattern":"unrecognised character (.+?) in duration (.+?)","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"crates/cli/src/metrics.rs","lineNumber":120,"sourceCode":"\n    for ch in s.chars() {\n        match ch {\n            '0'..='9' => num_buf.push(ch),\n            'd' | 'h' | 'm' | 's' => {\n                let n: i64 = num_buf\n                    .parse()\n                    .map_err(|_| anyhow::anyhow!(\"invalid duration component: {num_buf:?}\"))?;\n                num_buf.clear();\n                let factor = match ch {\n                    'd' => 86_400,\n                    'h' => 3_600,\n                    'm' => 60,\n                    's' => 1,\n                    _ => unreachable!(),\n                };\n                total += n * factor;\n            }\n            _ => anyhow::bail!(\"unrecognised character {ch:?} in duration {s:?}\"),\n        }\n    }\n\n    if !num_buf.is_empty() {\n        // Trailing bare number — treat as seconds.\n        let n: i64 = num_buf.parse()?;\n        total += n;\n    }\n\n    if total == 0 {\n        anyhow::bail!(\"duration {s:?} resolved to zero seconds\");\n    }\n\n    Ok(total)\n}\n\n// ──────────────────────────────────────────────────────────────────────────────\n// Rollup data model","sourceCodeStart":102,"sourceCodeEnd":138,"githubUrl":"https://github.com/Hmbown/CodeWhale/blob/73e0f67d83c59909b571efdfc88c4bc28c309cb1/crates/cli/src/metrics.rs#L102-L138","documentation":"`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.","triggerScenarios":"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.","commonSituations":"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.","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."],"exampleFix":"// before\ncli --since 5min\n// after\ncli --since 5m","handlingStrategy":"validation","validationCode":"let re = std::regex::Regex::new(r\"^[0-9]+[smh]?$\").unwrap();\nif !re.is_match(since) {\n    eprintln!(\"invalid --since '{since}'; use forms like 90, 90s, 5m, 2h\");\n    std::process::exit(2);\n}","typeGuard":null,"tryCatchPattern":"match parse_since(raw) {\n    Err(e) if e.to_string().contains(\"unrecognised character\") => {\n        eprintln!(\"{e}; supported units are single letters s/m/h\");\n    }\n    Err(e) => return Err(e),\n    Ok(secs) => use_since(secs),\n}","preventionTips":["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."],"tags":["cli","parsing","duration"],"backgroundTag":"invalid-duration-format","analyzedSha":"73e0f67d83c59909b571efdfc88c4bc28c309cb1","analyzedAt":"2026-09-22T01:30:00.501Z","contentChangedAt":"2026-09-22T01:30:00.501Z","schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}