{"record":{"id":"23a5bf9a3882af6c","repo":"Hmbown/CodeWhale","slug":"invalid-duration-component-num-buf","errorCode":null,"errorMessage":"invalid duration component: {num_buf:?}","messagePattern":"invalid duration component: (.+?)","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"warning","filePath":"crates/cli/src/metrics.rs","lineNumber":109,"sourceCode":"pub fn parse_since(s: &str) -> Result<DateTime<Utc>> {\n    let s = s.trim().to_ascii_lowercase();\n    let s = s.strip_prefix(\"now-\").unwrap_or(&s);\n    let secs = parse_duration_secs(s)?;\n    Ok(Utc::now() - Duration::seconds(secs))\n}\n\nfn parse_duration_secs(s: &str) -> Result<i64> {\n    // Walk through the string accumulating numbers and consuming unit suffixes.\n    let mut total: i64 = 0;\n    let mut num_buf = String::new();\n\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;","sourceCodeStart":91,"sourceCodeEnd":127,"githubUrl":"https://github.com/Hmbown/CodeWhale/blob/73e0f67d83c59909b571efdfc88c4bc28c309cb1/crates/cli/src/metrics.rs#L91-L127","documentation":"parse_duration_secs parses human-friendly duration strings like \"1d2h30m\". When it accumulates digits and hits a unit character, it tries to parse the accumulated buffer as i64. If the buffer is empty (e.g. \"dm\") or contains only non-numeric leftover characters, this error is thrown naming the offending buffer with {:?}.","triggerScenarios":"A --since/-since duration value contains a unit character not preceded by a number, e.g. \"h30\", \"1d2m..s\", or stray letters like \"1dxh\" that leave an invalid buffer such as \"x\".","commonSituations":"Typo in a CLI --since flag (\"since=2hh\", \"since=1d 2h\" with a space producing an empty buffer), or passing a timestamp string where a duration was expected.","solutions":["Fix the duration string so every unit (d/h/m/s) is preceded by digits, e.g. \"1d2h30m\".","Remove stray non-numeric characters between number and unit.","If you meant an absolute timestamp, use the timestamp form of --since instead of a duration.","Quote the value in your shell so spaces do not split the argument."],"exampleFix":"// before\n--since 1d 2h   // buffer becomes empty/garbage between tokens\n// after\n--since 1d2h","handlingStrategy":"validation","validationCode":"// regex pre-check for well-formed duration\nconst ok = /^(\\d+[dhms])+$/.test(value);","typeGuard":null,"tryCatchPattern":"try {\n  let secs = parseSince(raw);\n} catch (e) {\n  if (String(e).includes('invalid duration component')) {\n    console.error(`Duration must look like 1d2h30m: ${raw}`);\n  }\n}","preventionTips":["Use the canonical <number><unit> form with no spaces: 1d2h30m.","Quote duration arguments in shell scripts.","Only pass timestamps where durations are expected, and vice versa."],"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"}