{"record":{"id":"d65c1e1a8303f79d","repo":"astrid-runtime/astrid","slug":"unknown-duration-suffix-other","errorCode":null,"errorMessage":"unknown duration suffix: {other}","messagePattern":"unknown duration suffix: (.+?)","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"crates/astrid-cli/src/commands/quota.rs","lineNumber":419,"sourceCode":"        suffix.push(c);\n        while let Some(&n) = iter.peek() {\n            if n.is_ascii_alphabetic() {\n                suffix.push(n);\n                iter.next();\n            } else {\n                break;\n            }\n        }\n        let num: f64 = current\n            .parse()\n            .with_context(|| format!(\"invalid duration component: {current}\"))?;\n        let chunk = match suffix.to_ascii_lowercase().as_str() {\n            \"ms\" => Duration::from_secs_f64(num / 1000.0),\n            \"s\" => Duration::from_secs_f64(num),\n            \"m\" => Duration::from_secs_f64(num * 60.0),\n            \"h\" => Duration::from_secs_f64(num * 3600.0),\n            \"d\" => Duration::from_secs_f64(num * 86_400.0),\n            other => anyhow::bail!(\"unknown duration suffix: {other}\"),\n        };\n        total = total.saturating_add(chunk);\n        current.clear();\n    }\n    if !current.is_empty() {\n        // Trailing bare number without suffix → seconds.\n        let secs: u64 = current.parse().context(\"trailing number without suffix\")?;\n        total = total.saturating_add(Duration::from_secs(secs));\n    }\n    Ok(total)\n}\n\n/// Render a byte count as a human-readable string with binary units.\n#[expect(\n    clippy::cast_precision_loss,\n    reason = \"human-readable rendering, magnitude up to ~GiB\"\n)]\nfn format_bytes(b: u64) -> String {","sourceCodeStart":401,"sourceCodeEnd":437,"githubUrl":"https://github.com/astrid-runtime/astrid/blob/affd8760f44190dbdfbec23403f4c4b642c33112/crates/astrid-cli/src/commands/quota.rs#L401-L437","documentation":"Unknown-suffix arm of parse_duration's chunk parser: the trailing alphabetic unit after a numeric component (case-insensitively) is not one of ms, s, m, h, or their accepted variants. E.g. \"30sec\" or \"2w\". The parser fails rather than guessing a unit, since misinterpreting a duration would corrupt quota TTLs.","triggerScenarios":"`--timeout 5min`, `--timeout 2hr`, `--timeout 1w`, or mixing in unsupported units in compound forms like `1h30mins`.","commonSituations":"Writing natural-language units (min, sec, weeks), translating from other tools' duration syntax (Go accepts `us`/`µs`, this parser does not), or typos like `30sec`.","solutions":["Use only ms, s, m, h, d suffixes (e.g. `5m` not `5min`)","Split unsupported units: `1w` -> `7d`","Remember bare integers are treated as seconds, so `300` works for 5 minutes"],"exampleFix":"// before\nastrid quota set --timeout 5min\n// after\nastrid quota set --timeout 5m","handlingStrategy":"validation","validationCode":"const DUR_SUFFIXES: [&str; 5] = [\"MS\",\"S\",\"M\",\"H\",\"D\"];\nfn chunks_valid(spec: &str) -> bool {\n    spec.split(|c: char| c.is_ascii_digit() || c == '.')\n        .filter(|p| !p.is_empty())\n        .all(|s| DUR_SUFFIXES.contains(&s.to_ascii_uppercase().as_str()))\n}","typeGuard":null,"tryCatchPattern":null,"preventionTips":["Use only ms/s/m/h/d in duration strings shared across tools","Normalize other tools' syntax (e.g. Go's `us`) before passing in","Add a pre-flight parser check in scripts that build duration flags"],"tags":["cli","parsing","duration"],"backgroundTag":"invalid-duration-format","analyzedSha":"affd8760f44190dbdfbec23403f4c4b642c33112","analyzedAt":"2026-09-09T21:28:12.402Z","contentChangedAt":"2026-09-09T21:28:12.402Z","schemaVersion":2},"datasetVersion":"2026-09-14T11:17:12.474Z"}