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.

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.

Appendix: 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)