{"record":{"id":"8e382176e1e9dec3","repo":"sgl-project/sglang","slug":"material-uri-has-an-invalid-percent-escape","errorCode":null,"errorMessage":"material URI has an invalid percent escape","messagePattern":"material URI has an invalid percent escape","errorType":"validation","errorClass":"ValueError","httpStatus":null,"severity":"error","filePath":"python/sglang/multimodal_gen/runtime/pipelines_core/stages/model_specific_stages/minimax_h3/material_io.py","lineNumber":127,"sourceCode":"            if separator - payload_start > MINIMAX_H3_BASE64_HEADER_MAX_CHARS:\n                raise ValueError(\"base64 URI header is too large\")\n            header = uri[payload_start:separator]\n            media_type = header.split(\";\", 1)[0].lower() or None\n            payload_start = separator + 1\n    else:  # pragma: no cover - guarded by the caller\n        raise ValueError(\"not a base64 material URI\")\n    return payload_start, media_type\n\n\ndef _iter_base64_payload_bytes(uri: str, payload_start: int):\n    \"\"\"Yield validated, unquoted base64 bytes without copying the payload.\"\"\"\n\n    index = payload_start\n    while index < len(uri):\n        character = uri[index]\n        if character == \"%\":\n            if index + 2 >= len(uri):\n                raise ValueError(\"material URI has an invalid percent escape\")\n            try:\n                value = int(uri[index + 1 : index + 3], 16)\n            except ValueError as exc:\n                raise ValueError(\"material URI has an invalid percent escape\") from exc\n            index += 3\n            character = chr(value)\n        else:\n            index += 1\n\n        if character.isspace():\n            continue\n        if len(character) != 1 or ord(character) > 127:\n            raise ValueError(\"material URI base64 payload must be ASCII\")\n        value = ord(character)\n        if value not in _BASE64_ALPHABET:\n            raise ValueError(\n                f\"material URI has an invalid base64 character {character!r}\"\n            )","sourceCodeStart":109,"sourceCodeEnd":145,"githubUrl":"https://github.com/sgl-project/sglang/blob/0132848349585cfe6aae51c4941cbae872505f8a/python/sglang/multimodal_gen/runtime/pipelines_core/stages/model_specific_stages/minimax_h3/material_io.py#L109-L145","documentation":"While walking the payload of a base64 material URI, _iter_base64_payload_bytes decodes percent escapes. A '%' at the very end of the URI (fewer than 2 following characters) is an incomplete escape and raises this error, since the byte value cannot be recovered.","triggerScenarios":"A material URI payload ending in '%' or '%A' (truncated escape), e.g. from truncation of a long URI or naive string slicing of a payload.","commonSituations":"URI truncation at a fixed character limit (max-length enforcement, DB column, prompt token cap), manual string surgery on payloads, or LLM-generated URIs with stray percent signs.","solutions":["Truncate at base64 character boundaries (multiples of 4, no '%' involved) instead of raw string slicing","Re-encode or regenerate the material URI from the original bytes","Validate percent escapes with a regex like /%(?:[0-9a-fA-F]{2})/ before submission"],"exampleFix":"// before\nuri = full_uri[:1024]  # may cut mid-escape\n// after\nassert re.fullmatch(r'(?:%[0-9a-fA-F]{2}|[A-Za-z0-9+/=])*', uri)\n# or regenerate: uri = build_uri(blob)","handlingStrategy":"validation","validationCode":"import re\nif not re.fullmatch(r'(?:%[0-9a-fA-F]{2}|[^%])*', payload_part):\n    raise ValueError('payload has dangling percent escape')","typeGuard":"def payload_escapes_ok(payload: str) -> bool:\n    import re\n    return re.fullmatch(r'(?:%[0-9a-fA-F]{2}|[^%])*', payload) is not None","tryCatchPattern":null,"preventionTips":["Never slice base64 strings at arbitrary offsets","Regenerate URIs instead of truncating them"],"tags":["minimax-h3","percent-encoding","material-io","parsing"],"backgroundTag":"invalid-percent-encoding","analyzedSha":"0132848349585cfe6aae51c4941cbae872505f8a","analyzedAt":"2026-08-28T05:10:05.995Z","schemaVersion":2},"datasetVersion":"2026-08-28T06:17:29.519Z"}