sgl-project/sglang · error · ValueError
material URI has an invalid percent escape
Error message
material URI has an invalid percent escape
What it means
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.
Source
Thrown at python/sglang/multimodal_gen/runtime/pipelines_core/stages/model_specific_stages/minimax_h3/material_io.py:127
if separator - payload_start > MINIMAX_H3_BASE64_HEADER_MAX_CHARS:
raise ValueError("base64 URI header is too large")
header = uri[payload_start:separator]
media_type = header.split(";", 1)[0].lower() or None
payload_start = separator + 1
else: # pragma: no cover - guarded by the caller
raise ValueError("not a base64 material URI")
return payload_start, media_type
def _iter_base64_payload_bytes(uri: str, payload_start: int):
"""Yield validated, unquoted base64 bytes without copying the payload."""
index = payload_start
while index < len(uri):
character = uri[index]
if character == "%":
if index + 2 >= len(uri):
raise ValueError("material URI has an invalid percent escape")
try:
value = int(uri[index + 1 : index + 3], 16)
except ValueError as exc:
raise ValueError("material URI has an invalid percent escape") from exc
index += 3
character = chr(value)
else:
index += 1
if character.isspace():
continue
if len(character) != 1 or ord(character) > 127:
raise ValueError("material URI base64 payload must be ASCII")
value = ord(character)
if value not in _BASE64_ALPHABET:
raise ValueError(
f"material URI has an invalid base64 character {character!r}"
)View on GitHub (pinned to 0132848349)
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
Example fix
// before
uri = full_uri[:1024] # may cut mid-escape
// after
assert re.fullmatch(r'(?:%[0-9a-fA-F]{2}|[A-Za-z0-9+/=])*', uri)
# or regenerate: uri = build_uri(blob) Defensive patterns
Strategy: validation
Validate before calling
import re
if not re.fullmatch(r'(?:%[0-9a-fA-F]{2}|[^%])*', payload_part):
raise ValueError('payload has dangling percent escape') Type guard
def payload_escapes_ok(payload: str) -> bool:
import re
return re.fullmatch(r'(?:%[0-9a-fA-F]{2}|[^%])*', payload) is not None Prevention
- Never slice base64 strings at arbitrary offsets
- Regenerate URIs instead of truncating them
When it happens
Trigger: 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.
Common situations: 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.
Related errors
- data URI must contain a comma separator
- tar material URI must contain '<tar_path>:<encoded_header>'
- data URI header is too large
- data URI must use ;base64 encoding
- base64 URI header is too large
AI-assisted analysis of sgl-project/sglang@0132848349 (2026-08-28).
Data as JSON: /api/errors/8e382176e1e9dec3.
Report an issue: GitHub.