{"record":{"id":"6a75c32cd2b9d3f5","repo":"ArchiveBox/ArchiveBox","slug":"size-value-must-resolve-to-a-whole-number-of-bytes","errorCode":null,"errorMessage":"Size value must resolve to a whole number of bytes.","messagePattern":"Size value must resolve to a whole number of bytes\\.","errorType":"validation","errorClass":"ValueError","httpStatus":null,"severity":"error","filePath":"archivebox/misc/util.py","lineNumber":296,"sourceCode":"            yield url\n\n\ndef parse_filesize_to_bytes(value: str | int | float | None) -> int:\n    \"\"\"\n    Parse a byte count from an integer or human-readable string like 45mb or 2 GB.\n    \"\"\"\n    if value is None:\n        return 0\n\n    if isinstance(value, bool):\n        raise ValueError(\"Size value must be an integer or size string.\")\n\n    if isinstance(value, int):\n        return value\n\n    if isinstance(value, float):\n        if not value.is_integer():\n            raise ValueError(\"Size value must resolve to a whole number of bytes.\")\n        return int(value)\n\n    raw_value = str(value).strip()\n    if not raw_value:\n        return 0\n\n    if raw_value.isdigit():\n        return int(raw_value)\n\n    match = re.fullmatch(r\"(?i)(\\d+(?:\\.\\d+)?)\\s*([a-z]+)\", raw_value)\n    if not match:\n        raise ValueError(f\"Invalid size value: {value}\")\n\n    amount_str, unit_str = match.groups()\n    multiplier = FILESIZE_UNITS.get(unit_str.lower())\n    if multiplier is None:\n        raise ValueError(f\"Unknown size unit: {unit_str}\")\n","sourceCodeStart":278,"sourceCodeEnd":314,"githubUrl":"https://github.com/ArchiveBox/ArchiveBox/blob/74564b28220090664f919479e82cbf454125fa34/archivebox/misc/util.py#L278-L314","documentation":"parse_filesize_to_bytes accepts floats only if they represent a whole number of bytes; a fractional value like 1024.5 cannot be represented as an exact byte count, so it raises ValueError rather than silently truncating. This keeps size limits exact.","triggerScenarios":"Calling parse_filesize_to_bytes(1024.5) or a value computed by division (e.g. total/2.0) via clean_snapshot_max_size/clean_crawl_max_size or the add command.","commonSituations":"Computing sizes with division in a script then feeding the float to ArchiveBox config; JSON/YAML configs containing decimal sizes like 1.5mb entered as raw float 1572864.5; unit conversions done with float math.","solutions":["Round to an integer before passing: int(value) or round(value).","Use a size string with a supported unit instead of raw float bytes (e.g. '1.5gb' is handled by the string branch).","Fix the upstream computation to keep byte counts integral.","Sanitize config values with math.floor/ceil at load time."],"exampleFix":"// before\nhalf = total_bytes / 2\nparse_filesize_to_bytes(half)\n// after\nhalf = total_bytes // 2\nparse_filesize_to_bytes(half)","handlingStrategy":"type-guard","validationCode":"def whole_bytes_ok(v) -> bool:\n    if isinstance(v, float):\n        return v.is_integer()\n    return isinstance(v, (int, str)) and not isinstance(v, bool)","typeGuard":"def is_integral(v: object) -> bool:\n    return (isinstance(v, int) and not isinstance(v, bool)) or (isinstance(v, float) and v.is_integer())","tryCatchPattern":"try:\n    n = parse_filesize_to_bytes(value)\nexcept ValueError:\n    n = int(float(value))  # only if truncation is acceptable","preventionTips":["Use integer math (//) for size computations","Round before passing computed sizes","Prefer unit strings ('1.5gb') over raw fractional floats","Validate config values are whole numbers at load"],"tags":["python","validation","numbers"],"backgroundTag":"invalid-numeric-value","analyzedSha":"74564b28220090664f919479e82cbf454125fa34","analyzedAt":"2026-08-28T23:56:51.556Z","schemaVersion":2},"datasetVersion":"2026-08-29T02:17:18.158Z"}