666ghj/MiroFish · error · StarHistoryError

generated SVG root is invalid

Error message

generated SVG root is invalid

What it means

Raised by `_validate_svg` (scripts/star_history.py:1090) when the parsed XML root element is not `{http://www.w3.org/2000/svg}svg` — i.e. the document's outermost element is something else (a wrapper `<div>`, `<g>`, or an `<svg>` missing its namespace declaration). The validator then walks only known-namespaced children, so a wrong root aborts immediately.

Source

Thrown at scripts/star_history.py:1090

        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": {
            "viewBox",
            "width",
            "height",
            "preserveAspectRatio",
            "role",
            "aria-labelledby",
        },
        "title": {"id"},
        "desc": {"id"},
        "rect": {
            "x",
            "y",
            "width",
            "height",
            "rx",
            "fill",

View on GitHub (pinned to b5b53acc57)

Solutions

  1. Ensure the document's first and only root element is `<svg xmlns="http://www.w3.org/2000/svg" ...>` with the exact namespace URI.
  2. Remove wrapper elements — if you need surrounding markup, keep it outside the validated artifact and embed the SVG via `<img>`/`<object>` or inline after validation.
  3. If a sanitizer stripped xmlns, re-add it: `root.set("xmlns", "http://www.w3.org/2000/svg")` before serialization (or serialize with ET which handles namespaces).
  4. Check for accidental content before the root (comments are fine; elements are not).

Example fix

# before: wrapper element becomes the root
payload = b"<div>" + svg_body + b"</div>"

# after: svg stays the root; wrapper lives outside
svg = render_svg(state, theme)          # root is <svg xmlns=...>
html = b"<div>" + svg + b"</div>"       # embed after validation
Defensive patterns

Strategy: try-catch

Validate before calling

import xml.etree.ElementTree as ET
SVG_NS = "{http://www.w3.org/2000/svg}"

def has_svg_root(payload: bytes) -> bool:
    try:
        return ET.fromstring(payload).tag == f"{SVG_NS}svg"
    except ET.ParseError:
        return False

Try / catch

try:
    _validate_svg(payload)
except StarHistoryError as exc:
    if "root is invalid" in str(exc):
        raise ValueError("document must be a single root <svg xmlns=...> element") from exc
    raise

Prevention

When it happens

Trigger: Payload parses as XML but the root tag lacks `xmlns="http://www.w3.org/2000/svg"` (then the tag is plain `svg`, not the namespaced one), or the document is wrapped (e.g. someone put the whole chart inside `<html>` or prepended another element before `<svg>`).

Common situations: Forks that wrap the SVG in an outer container for embedding; templates that omit the xmlns attribute; tools that 'clean' the SVG and drop the namespace; embedding logic that concatenates a banner element before the svg root.

Related errors


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