{"record":{"id":"aae379ced853db73","repo":"astrid-runtime/astrid","slug":"byte-value-must-be-non-negative-and-finite","errorCode":null,"errorMessage":"byte value must be non-negative and finite","messagePattern":"byte value must be non-negative and finite","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"crates/astrid-cli/src/commands/quota.rs","lineNumber":347,"sourceCode":"}\n\n// ── byte/duration parsers ──────────────────────────────────────────\n\n/// Parse `\"32\"`, `\"32B\"`, `\"32KB\"`, `\"32MB\"`, `\"32GB\"`, `\"32KiB\"`,\n/// `\"32MiB\"`, `\"32GiB\"`, `\"32TB\"`, `\"32TiB\"`. Lowercase accepted.\npub(crate) fn parse_bytes(s: &str) -> Result<u64> {\n    let trimmed = s.trim();\n    if trimmed.is_empty() {\n        anyhow::bail!(\"empty byte specifier\");\n    }\n    // Strip optional `/s` (used by --ipc-rate).\n    let body = trimmed.strip_suffix(\"/s\").unwrap_or(trimmed);\n    let (num_part, mult) = parse_numeric_suffix(body)?;\n    let num: f64 = num_part\n        .parse()\n        .with_context(|| format!(\"not a number: {num_part}\"))?;\n    if num.is_sign_negative() || !num.is_finite() {\n        anyhow::bail!(\"byte value must be non-negative and finite\");\n    }\n    #[expect(\n        clippy::cast_sign_loss,\n        clippy::cast_precision_loss,\n        clippy::cast_possible_truncation,\n        reason = \"guarded by sign and finite checks above\"\n    )]\n    let bytes = (num * (mult as f64)) as u64;\n    Ok(bytes)\n}\n\nfn parse_numeric_suffix(body: &str) -> Result<(&str, u64)> {\n    // Find the index where the suffix (alphabetic or `i` for binary)\n    // begins. Consume digits and at most one `.`.\n    let split = body\n        .find(|c: char| !(c.is_ascii_digit() || c == '.'))\n        .unwrap_or(body.len());\n    let (num_part, suffix) = body.split_at(split);","sourceCodeStart":329,"sourceCodeEnd":365,"githubUrl":"https://github.com/astrid-runtime/astrid/blob/affd8760f44190dbdfbec23403f4c4b642c33112/crates/astrid-cli/src/commands/quota.rs#L329-L365","documentation":"After parsing the numeric part as f64, `parse_bytes` rejects negative, NaN, or infinite values because a byte quota must be a finite non-negative quantity. The cast to u64 is only safe because of this guard, so it fires before any conversion.","triggerScenarios":"`--storage -5MB`, `--storage inf`, or `--storage nan` (the numeric portion parses as f64 but fails the sign/finite check).","commonSituations":"Sign mistakes in scripts (e.g. computing a quota via subtraction that goes negative), copy-pasted `Infinity`/`NaN` from JSON configs, or typos like `-1GB` intending 'unlimited'.","solutions":["Provide a finite non-negative value, e.g. `--storage 1GB`","Fix the calculation producing the negative/NaN number in your script","Use the documented 'unset/remove quota' mechanism instead of a sentinel like -1"],"exampleFix":"// before\nastrid quota set --storage -2GB\n// after\nastrid quota set --storage 2GB","handlingStrategy":"validation","validationCode":"let n: f64 = /* parsed number */;\nif !n.is_finite() || n.is_sign_negative() {\n    return Err(\"byte value must be finite and >= 0\".into());\n}","typeGuard":null,"tryCatchPattern":null,"preventionTips":["Sanity-check computed quotas (assert > 0) before applying them","Avoid sentinel negative values for 'unlimited'; use the tool's unset mechanism","Beware float math that can yield NaN/inf in provisioning scripts"],"tags":["cli","parsing","validation"],"backgroundTag":"invalid-argument-value","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"}