666ghj/MiroFish · error · StarHistoryError

could not create output directory

Error message

could not create output directory

What it means

Raised by _safe_target when Path.mkdir(parents=True, exist_ok=True) on the output's parent directory raises OSError. The raw OS error is chained (__cause__) so the underlying errno is preserved in the traceback.

Source

Thrown at scripts/star_history.py:481

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)
    except (OSError, ValueError) as exc:
        raise StarHistoryError("output path escaped the workspace") from exc
    return resolved_parent / target.name


def _read_limited(path: Path, limit: int, label: str) -> bytes:
    try:
        with path.open("rb") as handle:

View on GitHub (pinned to b5b53acc57)

Solutions

  1. Inspect exc.__cause__ from the StarHistoryError to get the errno
  2. Ensure the invoking user has write permission on the workspace (or run with appropriate ownership)
  3. Remove any regular file occupying the directory path: find . -name 'star-history' -type f
  4. Free disk space / raise quota, or point the workspace at a writable location

Example fix

# before: a file blocks the directory path
.github/star-history  (regular file)
# after
rm .github/star-history && mkdir -p .github/star-history
Defensive patterns

Strategy: try-catch

Validate before calling

parent = (ws / rel).parent
if parent.exists() and not parent.is_dir():
    raise RuntimeError(f"{parent} exists but is not a directory")
import os
if not os.access(ws, os.W_OK):
    raise RuntimeError("workspace not writable")

Try / catch

try:
    _safe_target(ws, rel, create_parent=True)
except StarHistoryError as e:
    if str(e) == "could not create output directory" and e.__cause__:
        errno = e.__cause__.errno  # EACCES/ENOSPC/EEXIST/EROFS -> targeted fix
    raise

Prevention

When it happens

Trigger: Calling _safe_target(workspace, relative, create_parent=True) (i.e. save paths) when the parent chain cannot be created: EACCES/EPERM on the workspace dir, ENOSPC (disk full), EEXIST because a path component already exists as a regular file (e.g. a file literally named '.github/star-history'), EROFS on a read-only filesystem, or EDQUOT quota exceeded.

Common situations: Running the tool in CI as a non-owner user against a checked-out repo, disk-full build agents, a stray file named like the state directory, or containers with a read-only layer at that path.

Related errors


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