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

  1. Truncate at base64 character boundaries (multiples of 4, no '%' involved) instead of raw string slicing
  2. Re-encode or regenerate the material URI from the original bytes
  3. 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

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


AI-assisted analysis of sgl-project/sglang@0132848349 (2026-08-28). Data as JSON: /api/errors/8e382176e1e9dec3. Report an issue: GitHub.