666ghj/MiroFish · error · StarHistoryError
output directory cannot be a symbolic link
Error message
output directory cannot be a symbolic link
What it means
Raised by _safe_target in scripts/star_history.py when a directory component of a relative output path (e.g. '.github/star-history/' inside history.json's path) is a symbolic link. The tool refuses to traverse symlinks anywhere in the output directory chain so a compromised or misconfigured workspace cannot redirect state writes outside the workspace root.
Source
Thrown at scripts/star_history.py:473
try:
root = workspace.resolve(strict=True)
except OSError as exc:
raise StarHistoryError("workspace does not exist") from exc
if not root.is_dir():
raise StarHistoryError("workspace is not a directory")
return root
def _safe_target(workspace: Path, relative: Path, create_parent: bool) -> Path:
root = _safe_workspace(workspace)
if relative.is_absolute() or ".." in relative.parts:
raise StarHistoryError("output path escaped the workspace")
current = root
for part in relative.parts[:-1]:
current = current / part
if current.is_symlink():
raise StarHistoryError("output directory cannot be a symbolic link")
target = root / relative
if target.is_symlink():
raise StarHistoryError("output file cannot be a symbolic link")
if create_parent:
try:
target.parent.mkdir(parents=True, exist_ok=True)
except OSError as exc:
raise StarHistoryError("could not create output directory") from exc
current = root
for part in relative.parts[:-1]:
current = current / part
if current.is_symlink():
raise StarHistoryError("output directory cannot be a symbolic link")
if target.is_symlink():
raise StarHistoryError("output file cannot be a symbolic link")
try:
resolved_parent = target.parent.resolve(strict=False)
resolved_parent.relative_to(root)View on GitHub (pinned to b5b53acc57)
Solutions
- Run: ls -la .github .github/star-history in the workspace to find the offending link
- Replace the symlink with a real directory (mkdir + copy contents, then rm the link)
- Re-run the command; if it recurs, check for processes recreating the link
- If you intentionally need linked storage, bind-mount instead of symlinking
Example fix
# before .github -> ../shared-config/.github # after (real directory) mkdir -p .github/star-history && cp -r ../shared-config/.github/* .github/
Defensive patterns
Strategy: validation
Validate before calling
def assert_no_dir_symlinks(root: Path, relative: Path) -> None:
cur = root.resolve(strict=True)
for part in relative.parts[:-1]:
cur = cur / part
if cur.is_symlink():
raise RuntimeError(f"symlinked directory: {cur}") Try / catch
try:
target = _safe_target(ws, rel, create_parent=True)
except StarHistoryError as e:
if str(e) == "output directory cannot be a symbolic link":
fix_and_report(e) # inspect .github chain
raise Prevention
- Never symlink .github or .github/star-history; use real directories
- Bind-mount shared config instead of symlinking
- Run ls -la .github before invoking state-writing steps
- Lock the workspace so cleanup jobs cannot swap dirs mid-run
When it happens
Trigger: Calling any API that resolves an output target (_safe_target, or indirectly load_state/save_state with a workspace whose .github or .github/star-history is a symlinked directory). Both the pre-check (line 472) and the post-mkdir re-check (line 485) raise this message.
Common situations: Developers symlink .github into a shared dotfiles repo, use monorepo workspaces where .github is a link to a templates dir, or CI caches that materialize directories as links. Also a symlink-swap attack between the mkdir and the write.
Related errors
- output file cannot be a symbolic link
- output path escaped the workspace
- could not create output directory
- could not read {label}
- Star count file is missing or unsafe
AI-assisted analysis of 666ghj/MiroFish@b5b53acc57 (2026-08-14).
Data as JSON: /api/errors/8e4d0886ce898455.
Report an issue: GitHub.