{"record":{"id":"91ee3ee955a53547","repo":"sgl-project/sglang","slug":"expected-base64-encoded-bytes","errorCode":null,"errorMessage":"Expected base64-encoded bytes","messagePattern":"Expected base64-encoded bytes","errorType":"validation","errorClass":"ValueError","httpStatus":null,"severity":"error","filePath":"python/sglang/srt/utils/msgspec_utils.py","lineNumber":27,"sourceCode":"from pydantic_core import core_schema\n\n\nclass Base64Bytes:\n    \"\"\"Pydantic marker for HTTP JSON base64-encoded bytes fields.\"\"\"\n\n    def __get_pydantic_core_schema__(self, source_type: Any, handler):\n        return core_schema.no_info_before_validator_function(\n            self._decode_value,\n            handler(source_type),\n        )\n\n    @classmethod\n    def _decode_value(cls, value: Any) -> Any:\n        if isinstance(value, str):\n            try:\n                return base64.b64decode(value, validate=True)\n            except binascii.Error as exc:\n                raise ValueError(\"Expected base64-encoded bytes\") from exc\n\n        if isinstance(value, list):\n            return [cls._decode_value(item) for item in value]\n\n        if isinstance(value, tuple):\n            return tuple(cls._decode_value(item) for item in value)\n\n        return value\n\n\ndef msgspec_to_builtins(obj: Any) -> Any:\n    \"\"\"Recursively convert msgspec structs and dataclasses to builtins.\"\"\"\n    if isinstance(obj, msgspec.Struct):\n        return {\n            field.name: msgspec_to_builtins(getattr(obj, field.name))\n            for field in msgspec.structs.fields(type(obj))\n        }\n","sourceCodeStart":9,"sourceCodeEnd":45,"githubUrl":"https://github.com/sgl-project/sglang/blob/0132848349585cfe6aae51c4941cbae872505f8a/python/sglang/srt/utils/msgspec_utils.py#L9-L45","documentation":"msgspec_utils' _decode_value expects string fields to be base64-encoded bytes and decodes them with strict validation. A string containing characters outside the base64 alphabet (or bad padding) raises ValueError('Expected base64-encoded bytes'). Lists/tuples are decoded recursively, so a nested bad string also triggers it.","triggerScenarios":"Decoding a msgspec/json payload where a field intended to be base64 bytes actually holds free-form text — e.g., a filename, UUID with dashes is fine but a URL/path with ':' or '/' is not.","commonSituations":"Schema drift: a field changed from plain string to bytes-encoded and old payloads are replayed; or a caller puts a human-readable string where the wire format requires base64.","solutions":["base64-encode byte fields on the producer side: base64.b64encode(b).decode().","If the field is genuinely text, move it to a non-bytes field in the schema so _decode_value never touches it.","For debugging, catch binascii.Error context to identify which field failed."],"exampleFix":"# before\nmsg = {\"payload\": \"hello world\"}   # not valid base64 (space)\n\n# after\nimport base64\nmsg = {\"payload\": base64.b64encode(b\"hello world\").decode()}","handlingStrategy":"validation","validationCode":"import base64, binascii\ntry:\n    base64.b64decode(s, validate=True)\nexcept (binascii.Error, ValueError):\n    raise ValueError(f\"field is not base64: {s!r}\")","typeGuard":"import base64\n\ndef is_base64_str(s: str) -> bool:\n    try:\n        base64.b64decode(s, validate=True)\n        return True\n    except Exception:\n        return False","tryCatchPattern":null,"preventionTips":["Always b64encode bytes fields on the producer.","Keep text and bytes fields strictly separated in schemas."],"tags":["serialization","base64","msgspec"],"backgroundTag":"base64-decode-failed","analyzedSha":"0132848349585cfe6aae51c4941cbae872505f8a","analyzedAt":"2026-08-28T05:10:05.995Z","schemaVersion":2},"datasetVersion":"2026-08-28T06:17:29.519Z"}