{"record":{"id":"8e67b667ca4b674f","repo":"666ghj/MiroFish","slug":"star-count-file-is-missing-or-unsafe","errorCode":null,"errorMessage":"Star count file is missing or unsafe","messagePattern":"Star count file is missing or unsafe","errorType":"exception","errorClass":"StarHistoryError","httpStatus":null,"severity":"error","filePath":"scripts/star_history.py","lineNumber":519,"sourceCode":"    except FileNotFoundError as exc:\n        raise StarHistoryError(f\"{label} is missing\") from exc\n    except OSError as exc:\n        raise StarHistoryError(f\"could not read {label}\") from exc\n    if len(payload) > limit:\n        raise StarHistoryError(f\"{label} exceeded the size limit\")\n    return payload\n\n\ndef load_star_count_file(path: Path) -> int:\n    \"\"\"Read a tiny, symlink-safe decimal count produced by the fetch-only step.\"\"\"\n\n    flags = os.O_RDONLY\n    if hasattr(os, \"O_NOFOLLOW\"):\n        flags |= os.O_NOFOLLOW\n    try:\n        descriptor = os.open(path, flags)\n    except OSError as exc:\n        raise StarHistoryError(\"Star count file is missing or unsafe\") from exc\n    try:\n        metadata = os.fstat(descriptor)\n        if not stat.S_ISREG(metadata.st_mode):\n            raise StarHistoryError(\"Star count file is not a regular file\")\n        payload = os.read(descriptor, MAX_COUNT_FILE_BYTES + 1)\n    except OSError as exc:\n        raise StarHistoryError(\"could not read Star count file\") from exc\n    finally:\n        os.close(descriptor)\n\n    if len(payload) > MAX_COUNT_FILE_BYTES:\n        raise StarHistoryError(\"Star count file exceeded the size limit\")\n    if not re.fullmatch(rb\"(?:0|[1-9][0-9]*)\\n?\", payload):\n        raise StarHistoryError(\"Star count file must contain one decimal integer\")\n    count = int(payload)\n    if count > MAX_STAR_COUNT:\n        raise StarHistoryError(\"Star count exceeded the supported range\")\n    return count","sourceCodeStart":501,"sourceCodeEnd":537,"githubUrl":"https://github.com/666ghj/MiroFish/blob/b5b53acc57189a4a42e44a23e149dc655c98fe82/scripts/star_history.py#L501-L537","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","solutions":["Run the fetch-only step first so the count file exists as a regular file","readlink the path — if it is a symlink, replace it with the real file","Verify the exact path/working directory passed to load_star_count_file","Transfer the artifact between pipeline stages if fetch and render run apart"],"exampleFix":"# before\nrender(count=load_star_count_file(p))\n# after\nsubprocess.run(['python','scripts/fetch_star_count.py','--out',str(p)])\nrender(count=load_star_count_file(p))","handlingStrategy":"validation","validationCode":"import os\ndef count_readable(p: Path) -> bool:\n    try:\n        st = os.lstat(p)\n    except OSError:\n        return False\n    import stat as s\n    return s.S_ISREG(st.st_mode) and os.access(p, os.R_OK)","typeGuard":null,"tryCatchPattern":"except StarHistoryError as e:\n    if str(e) == \"Star count file is missing or unsafe\":\n        run_fetch_step(); count = load_star_count_file(p)  # produce it, retry\n    else: raise","preventionTips":["Make fetch a mandatory pipeline stage before render","Transfer the count artifact between stages explicitly","Never symlink the count file into place","Validate the artifact in CI before consuming it"],"tags":["state","symlink","file-not-found","pipeline"],"backgroundTag":null,"analyzedSha":"b5b53acc57189a4a42e44a23e149dc655c98fe82","analyzedAt":"2026-08-14T22:29:33.146Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}