666ghj/MiroFish · error · StarHistoryError

Star count file is missing or unsafe

Error message

Star count file is missing or unsafe

What it means

load_star_count_file opens the count file with O_RDONLY|O_NOFOLLOW; any OSError from os.open — including missing file, ELOOP (final component is a symlink, which O_NOFOLLOW rejects), EACCES — is reported as 'Star count file is missing or unsafe'. The wording reflects that the reader deliberately cannot distinguish benign absence from a symlink attack.

Source

Thrown at scripts/star_history.py:519

    except FileNotFoundError as exc:
        raise StarHistoryError(f"{label} is missing") from exc
    except OSError as exc:
        raise StarHistoryError(f"could not read {label}") from exc
    if len(payload) > limit:
        raise StarHistoryError(f"{label} exceeded the size limit")
    return payload


def load_star_count_file(path: Path) -> int:
    """Read a tiny, symlink-safe decimal count produced by the fetch-only step."""

    flags = os.O_RDONLY
    if hasattr(os, "O_NOFOLLOW"):
        flags |= os.O_NOFOLLOW
    try:
        descriptor = os.open(path, flags)
    except OSError as exc:
        raise StarHistoryError("Star count file is missing or unsafe") from exc
    try:
        metadata = os.fstat(descriptor)
        if not stat.S_ISREG(metadata.st_mode):
            raise StarHistoryError("Star count file is not a regular file")
        payload = os.read(descriptor, MAX_COUNT_FILE_BYTES + 1)
    except OSError as exc:
        raise StarHistoryError("could not read Star count file") from exc
    finally:
        os.close(descriptor)

    if len(payload) > MAX_COUNT_FILE_BYTES:
        raise StarHistoryError("Star count file exceeded the size limit")
    if not re.fullmatch(rb"(?:0|[1-9][0-9]*)\n?", payload):
        raise StarHistoryError("Star count file must contain one decimal integer")
    count = int(payload)
    if count > MAX_STAR_COUNT:
        raise StarHistoryError("Star count exceeded the supported range")
    return count

View on GitHub (pinned to b5b53acc57)

Solutions

  1. Run the fetch-only step first so the count file exists as a regular file
  2. readlink the path — if it is a symlink, replace it with the real file
  3. Verify the exact path/working directory passed to load_star_count_file
  4. Transfer the artifact between pipeline stages if fetch and render run apart

Example fix

# before
render(count=load_star_count_file(p))
# after
subprocess.run(['python','scripts/fetch_star_count.py','--out',str(p)])
render(count=load_star_count_file(p))
Defensive patterns

Strategy: validation

Validate before calling

import os
def count_readable(p: Path) -> bool:
    try:
        st = os.lstat(p)
    except OSError:
        return False
    import stat as s
    return s.S_ISREG(st.st_mode) and os.access(p, os.R_OK)

Try / catch

except StarHistoryError as e:
    if str(e) == "Star count file is missing or unsafe":
        run_fetch_step(); count = load_star_count_file(p)  # produce it, retry
    else: raise

Prevention

When it happens

Trigger: Calling load_star_count_file(path) before the fetch-only step (scripts/fetch_star_count.py) has written the count file, passing the wrong path, the file being a symlink (O_NOFOLLOW raises ELOOP), or lacking read permission.

Common situations: Split pipelines where fetch runs on one machine and render on another and the artifact was not transferred; count file symlinked from a shared cache; wrong working directory making the relative path miss.

Related errors


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