666ghj/MiroFish · error · StarHistoryError

generated SVG must contain both reviewed images

Error message

generated SVG must contain both reviewed images

What it means

Raised by _validate_svg after iterating the whole document: the exact-match counters for the reviewed avatar <image> and watermark <image> must each equal 1. Any deviation — zero occurrences (template dropped one), duplicates, or images whose attribute dicts no longer byte-match the pinned sets at scripts/star_history.py:1199-1213 — fails here or earlier with 'unreviewed image'.

Source

Thrown at scripts/star_history.py:1249

            exact_values = exact_attribute_values.get(raw_name)
            if exact_values is not None and value not in exact_values:
                raise StarHistoryError("generated SVG contains an unsafe attribute value")
            if raw_name == "href":
                if local_name != "image" or value not in {
                    OWNER_AVATAR_DATA_URI,
                    WATERMARK_LOGO_DATA_URI,
                }:
                    raise StarHistoryError("generated SVG contains an external resource")
            lowered = value.lower().replace(" ", "")
            if "url(" in lowered and lowered not in {
                "url(#xkcdify)",
                "url(#clip-circle-title)",
            }:
                raise StarHistoryError("generated SVG contains an external resource")
            if lowered.startswith(("http:", "https:", "//")):
                raise StarHistoryError("generated SVG contains an external resource")
    if avatar_count != 1 or watermark_count != 1:
        raise StarHistoryError("generated SVG must contain both reviewed images")
    try:
        avatar = base64.b64decode(OWNER_AVATAR_BASE64, validate=True)
    except ValueError as exc:
        raise StarHistoryError("reviewed avatar data is invalid") from exc
    if (
        len(avatar) > MAX_INLINE_AVATAR_BYTES
        or hashlib.sha256(avatar).hexdigest() != OWNER_AVATAR_SHA256
        or _reviewed_avatar_dimensions(avatar) != OWNER_AVATAR_DIMENSIONS
    ):
        raise StarHistoryError("reviewed avatar data is invalid")
    try:
        watermark = base64.b64decode(WATERMARK_LOGO_BASE64, validate=True)
    except ValueError as exc:
        raise StarHistoryError("reviewed watermark data is invalid") from exc
    png_header = (
        watermark.startswith(b"\x89PNG\r\n\x1a\n")
        and watermark[8:12] == (13).to_bytes(4, "big")
        and watermark[12:16] == b"IHDR"

View on GitHub (pinned to b5b53acc57)

Solutions

  1. Ensure every rendered SVG variant contains exactly one <image> matching avatar_attributes and one matching watermark_attributes (scripts/star_history.py:1199-1213), attribute-for-attribute.
  2. If you intentionally moved the avatar/watermark (new coordinates), update both the pinned attribute dicts in the validator and the template in the same change.
  3. If you intentionally removed the watermark, that violates the shipped design contract — restore it rather than patching the check.
  4. Run _validate_svg over both light and dark outputs after any template edit (they are validated in _write_outputs at lines 1326-1327).

Example fix

# before: watermark image deleted from the dark template
# (watermark_count == 0 at the end of _validate_svg)

# after: keep exactly one pinned watermark <image> per SVG
<image x="635" y="508.333" width="20" height="20"
       href="{WATERMARK_LOGO_DATA_URI}"/>
Defensive patterns

Strategy: validation

Validate before calling

def has_both_reviewed_images(root) -> bool:
    avatar = watermark = 0
    for el in root.iter():
        if el.tag.endswith("}image"):
            attrs = dict(el.attrib)
            if attrs == AVATAR_ATTRIBUTES:      # pinned dicts from the validator
                avatar += 1
            elif attrs == WATERMARK_ATTRIBUTES:
                watermark += 1
    return avatar == 1 and watermark == 1

Try / catch

try:
    _validate_svg(svg_bytes)
except StarHistoryError as exc:
    if "reviewed images" in str(exc):
        # count <image> elements; restore or re-pin the avatar/watermark blocks
        ...

Prevention

When it happens

Trigger: Deleting the watermark image from the template; moving/duplicating the avatar; changing any of its pinned attributes (x/y/width/height/clip-path) so element.attrib != avatar_attributes/watermark_attributes exactly (those cases raise 'unreviewed image' at line 1219, then this fires if the count is 0); rendering a variant (e.g. a banner without watermark).

Common situations: Someone removes the 'star-history.com' watermark for aesthetics; the avatar image attributes are reordered or an attribute added (attrib dict comparison fails); a new chart variant is added to _output_payloads that was never wired to include both images.

Related errors


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