{"record":{"id":"9ffcd8f06dff987a","repo":"astrid-runtime/astrid","slug":"empty-duration","errorCode":null,"errorMessage":"empty duration","messagePattern":"empty duration","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"crates/astrid-cli/src/commands/quota.rs","lineNumber":386,"sourceCode":"        \"K\" | \"KB\" => 1_000,\n        \"KIB\" => 1024,\n        \"M\" | \"MB\" => 1_000_000,\n        \"MIB\" => 1024 * 1024,\n        \"G\" | \"GB\" => 1_000_000_000,\n        \"GIB\" => 1024 * 1024 * 1024,\n        \"T\" | \"TB\" => 1_000_000_000_000,\n        \"TIB\" => 1024_u64.pow(4),\n        other => anyhow::bail!(\"unknown byte suffix: {other}\"),\n    };\n    Ok((num_part, mult))\n}\n\n/// Parse `\"30s\"`, `\"5m\"`, `\"1h\"`, `\"2h30m\"`, `\"500ms\"`. Falls back to\n/// seconds for a bare integer.\npub(crate) fn parse_duration(s: &str) -> Result<Duration> {\n    let trimmed = s.trim();\n    if trimmed.is_empty() {\n        anyhow::bail!(\"empty duration\");\n    }\n    if let Ok(secs) = trimmed.parse::<u64>() {\n        return Ok(Duration::from_secs(secs));\n    }\n    let mut total = Duration::ZERO;\n    let mut current = String::new();\n    let mut iter = trimmed.chars().peekable();\n    while let Some(c) = iter.next() {\n        if c.is_ascii_digit() || c == '.' {\n            current.push(c);\n            continue;\n        }\n        // Collect alpha suffix.\n        let mut suffix = String::new();\n        suffix.push(c);\n        while let Some(&n) = iter.peek() {\n            if n.is_ascii_alphabetic() {\n                suffix.push(n);","sourceCodeStart":368,"sourceCodeEnd":404,"githubUrl":"https://github.com/astrid-runtime/astrid/blob/affd8760f44190dbdfbec23403f4c4b642c33112/crates/astrid-cli/src/commands/quota.rs#L368-L404","documentation":"Input validation at the top of parse_duration: the duration specifier, after trimming, is empty. The parser accepts forms like 30s, 5m, 2h30m, 500ms or a bare integer (seconds); an empty value cannot be interpreted as any duration and quota-set aborts. Fires before any suffix parsing.","triggerScenarios":"Passing an empty/whitespace-only value to a duration-parsing quota flag in `astrid quota set` (e.g. an empty timeout/idle flag).","commonSituations":"Unset environment variable interpolated as empty in CI scripts, blank YAML/JSON field passed through, or a typo leaving the flag value empty.","solutions":["Provide an explicit duration, e.g. `--timeout 30s`","Default empty variables in the shell: `${TIMEOUT:-60}`","Validate config inputs before invoking the CLI"],"exampleFix":"// before\nastrid quota set --timeout \"$IDLE\"\n// after\nastrid quota set --timeout \"${IDLE:-30s}\"","handlingStrategy":"validation","validationCode":"fn ensure_duration(s: &str) -> Result<&str, String> {\n    let t = s.trim();\n    if t.is_empty() { Err(\"duration must not be empty\".into()) } else { Ok(t) }\n}","typeGuard":null,"tryCatchPattern":null,"preventionTips":["Default duration env vars in shell: ${IDLE_TIMEOUT:-30s}","Reject blank duration fields during config-file validation","Pass explicit unit suffixes rather than relying on empty inputs"],"tags":["cli","parsing","duration"],"backgroundTag":"empty-required-field","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"}