calesthio/OpenMontage · error · FileNotFoundError

Playbook not found: {path}

Error message

Playbook not found: {path}

What it means

FileNotFoundError raised by load_playbook when the requested style playbook YAML (<name>.yaml) does not exist under STYLES_DIR (or the styles_dir override). It is a plain existence check before yaml.safe_load, so the file was never opened — the failure is purely path/name resolution, not YAML parsing or schema validation.

Source

Thrown at styles/playbook_loader.py:46

def _load_playbook_schema() -> dict:
    with open(SCHEMA_PATH, encoding="utf-8") as f:
        return json.load(f)


def load_playbook(name: str, styles_dir: Optional[Path] = None) -> dict[str, Any]:
    """Load and validate a style playbook by name.

    Args:
        name: Playbook name (without .yaml extension).
        styles_dir: Override directory for playbook files.

    Returns:
        Validated playbook dict.
    """
    styles_dir = styles_dir or STYLES_DIR
    path = styles_dir / f"{name}.yaml"
    if not path.exists():
        raise FileNotFoundError(f"Playbook not found: {path}")

    with open(path, encoding="utf-8") as f:
        playbook = yaml.safe_load(f)

    validate_playbook(playbook)
    return playbook


def validate_playbook(playbook: dict) -> None:
    """Validate a playbook dict against the schema."""
    schema = _load_playbook_schema()
    jsonschema.validate(instance=playbook, schema=schema)


def list_playbooks(styles_dir: Optional[Path] = None) -> list[str]:
    """List all available playbook names."""
    styles_dir = styles_dir or STYLES_DIR
    return [

View on GitHub (pinned to 95e1c3d0ab)

Solutions

  1. List the styles directory (or call the registry/API that enumerates playbooks) and use an exact existing name
  2. If a custom playbook is intended, place <name>.yaml in styles_dir and ensure it passes validate_playbook
  3. Pass the name without the .yaml extension — the loader appends it
  4. For custom locations, pass styles_dir explicitly as an absolute Path

Example fix

# before
playbook = load_playbook("corparate-modern")  # typo

# after
from pathlib import Path
playbook = load_playbook("corporate-modern", styles_dir=Path("/abs/styles"))
Defensive patterns

Strategy: validation

Validate before calling

from pathlib import Path
def playbook_path(styles_dir: Path, name: str) -> Path:
    clean = name.removesuffix(".yaml").removesuffix(".yml")
    return styles_dir / f"{clean}.yaml"

p = playbook_path(STYLES_DIR, name)
if not p.exists():
    available = sorted(x.stem for x in STYLES_DIR.glob("*.yaml"))
    raise SystemExit(f"unknown playbook {name!r}; available: {available}")

Type guard

def playbook_exists(name: str, styles_dir: Path | None = None) -> bool:
    d = styles_dir or STYLES_DIR
    return (d / f"{name.removesuffix('.yaml')}.yaml").is_file()

Try / catch

try:
    playbook = load_playbook(name)
except FileNotFoundError:
    raise SystemExit(f"playbook {name!r} not found — check styles/ for valid names")

Prevention

When it happens

Trigger: Calling load_playbook(name) with a typo'd or nonexistent playbook name; pointing styles_dir at a custom directory that lacks the file; case-sensitivity mismatch of the name on Linux; referencing a playbook that was renamed or deleted from styles/.

Common situations: Docs reference a playbook removed in a refactor; user copies a config referencing a company playbook not shipped with the repo; running from a different working directory where a relative styles_dir override resolves incorrectly; name given with the .yaml extension included producing name.yaml.yaml.

Related errors


AI-assisted analysis of calesthio/OpenMontage@95e1c3d0ab (2026-08-15). Data as JSON: /api/errors/4fedb0c6e61bff8f. Report an issue: GitHub.