666ghj/MiroFish · error · StarHistoryError

generated SVG must be strict UTF-8

Error message

generated SVG must be strict UTF-8

What it means

Raised by the post-render self-check `_validate_svg` (scripts/star_history.py:1074) when the generated SVG bytes cannot be decoded as strict UTF-8. Everything the renderer emits should be ASCII/UTF-8 by construction, so this guard ensures the bytes about to be published are decodable before they reach badge consumers.

Source

Thrown at scripts/star_history.py:1074

    if OWNER_AVATAR_MEDIA_TYPE == "image/jpeg":
        if not payload.startswith(b"\xff\xd8\xff") or not payload.endswith(b"\xff\xd9"):
            return None
        for marker in (0xC0, 0xC1, 0xC2, 0xC3, 0xC5, 0xC6, 0xC7, 0xC9, 0xCA, 0xCB, 0xCD, 0xCE, 0xCF):
            offset = payload.find(bytes((0xFF, marker)))
            if offset >= 0 and offset + 9 <= len(payload):
                return (
                    int.from_bytes(payload[offset + 7 : offset + 9], "big"),
                    int.from_bytes(payload[offset + 5 : offset + 7], "big"),
                )
        return None
    return None


def _validate_svg(payload: bytes) -> None:
    try:
        decoded_payload = payload.decode("utf-8", errors="strict")
    except UnicodeDecodeError as exc:
        raise StarHistoryError("generated SVG must be strict UTF-8") from exc
    if decoded_payload.startswith("\ufeff") or "\x00" in decoded_payload:
        raise StarHistoryError("generated SVG must be canonical UTF-8")
    upper_payload = decoded_payload.upper()
    if (
        "<!DOCTYPE" in upper_payload
        or "<!ENTITY" in upper_payload
        or "<?" in decoded_payload
    ):
        raise StarHistoryError("generated SVG contains forbidden XML directives")
    try:
        root = ET.fromstring(decoded_payload)
    except ET.ParseError as exc:
        raise StarHistoryError("generated SVG is not valid XML") from exc
    svg_namespace = "{http://www.w3.org/2000/svg}"
    if root.tag != f"{svg_namespace}svg":
        raise StarHistoryError("generated SVG root is invalid")
    allowed_attributes: dict[str, set[str]] = {
        "svg": {

View on GitHub (pinned to b5b53acc57)

Solutions

  1. If you modified the renderer's text content, ensure all literals are UTF-8 (Python 3 source default) — avoid manually encoded byte splices.
  2. If validating external bytes, decode with `errors="strict"` and re-encode properly: `payload = payload.decode("utf-8").encode("utf-8")` only succeeds if it was UTF-8; otherwise fix the producer.
  3. Write SVG files in binary mode (`open(path, "wb")`) so no implicit codec re-encodes them.
  4. Keep unmodified `render_svg` output — its templates are pure ASCII, and this error then indicates post-processing corrupted the bytes.

Example fix

# before: text-mode write re-encodes on Windows (cp1252) -> later strict decode fails
with open(path, "w") as f:
    f.write(svg.decode("utf-8"))

# after: write bytes verbatim
with open(path, "wb") as f:
    f.write(svg)
Defensive patterns

Strategy: try-catch

Validate before calling

def is_strict_utf8(payload: bytes) -> bool:
    try:
        payload.decode("utf-8", errors="strict")
        return True
    except UnicodeDecodeError:
        return False

Try / catch

try:
    _validate_svg(payload)
except StarHistoryError as exc:
    if "strict UTF-8" in str(exc):
        raise ValueError("SVG bytes were re-encoded after generation; use render_svg output verbatim") from exc
    raise

Prevention

When it happens

Trigger: `render_svg` output being post-processed and re-encoded (e.g. re-saved with a legacy codec, concatenated behind a BOM-stripping step gone wrong), or a fork introducing non-UTF-8 bytes into text elements. Triggered only when `payload.decode("utf-8", errors="strict")` raises UnicodeDecodeError.

Common situations: Downstream code that opens the SVG file with `open(path, "w", encoding="latin-1")` and re-validates; piping the bytes through a tool that re-encodes; adding localized/emoji text to the renderer without keeping strings UTF-8.

Related errors


AI-assisted analysis of 666ghj/MiroFish@b5b53acc57 (2026-08-14). Data as JSON: /api/errors/e2ab0582372d5fb5. Report an issue: GitHub.