666ghj/MiroFish · error · StarHistoryError

workspace is not a directory

Error message

workspace is not a directory

What it means

Raised by _safe_workspace() when the workspace path resolves successfully but is not a directory (root.is_dir() is false) — e.g. it is a regular file, fifo, or device. The workspace is expected to be the containing directory under which output paths are confined.

Source

Thrown at scripts/star_history.py:460

            raise StarHistoryError("first snapshot cannot predate reconstruction")
        if previous_day is not None and previous_day >= first_snapshot.date():
            raise StarHistoryError("reconstruction dates must predate snapshots")


def canonical_state_bytes(state: Mapping[str, Any]) -> bytes:
    validate_state(state)
    return (
        json.dumps(state, ensure_ascii=False, indent=2, sort_keys=True) + "\n"
    ).encode("utf-8")


def _safe_workspace(workspace: Path) -> Path:
    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:

View on GitHub (pinned to b5b53acc57)

Solutions

  1. Point the workspace at the directory that should contain outputs, not at a file
  2. Remove the stray file occupying the workspace path if the directory was intended
  3. Re-check the CLI/config argument order and semantics

Example fix

# before
workspace = Path("out/star-history.json")  # this is a file
# after
workspace = Path("out")  # directory; pass the file name as the relative target
Defensive patterns

Strategy: validation

Validate before calling

workspace = Path(config["workspace"])
if not workspace.is_dir():
    raise NotADirectoryError(f"workspace is not a directory: {workspace}")

Type guard

from pathlib import Path

def is_workspace_dir(workspace: Path) -> bool:
    return workspace.is_dir()

Try / catch

try:
    write_output(workspace, relative, data)
except StarHistoryError as exc:
    if "workspace is not a directory" in str(exc):
        # fix configuration: point workspace at the parent directory
        ...

Prevention

When it happens

Trigger: Calling an output-writing code path with a workspace Path that names an existing file (e.g. pointing the workspace at the state JSON itself instead of its directory).

Common situations: Configuration confusion between the output file path and its parent directory; a file created at the location where the workspace directory was expected; swapped CLI arguments for workspace vs. file.

Related errors


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