{"record":{"id":"4fedb0c6e61bff8f","repo":"calesthio/OpenMontage","slug":"playbook-not-found-path","errorCode":null,"errorMessage":"Playbook not found: {path}","messagePattern":"Playbook not found: (.+?)","errorType":"exception","errorClass":"FileNotFoundError","httpStatus":null,"severity":"error","filePath":"styles/playbook_loader.py","lineNumber":46,"sourceCode":"def _load_playbook_schema() -> dict:\n    with open(SCHEMA_PATH, encoding=\"utf-8\") as f:\n        return json.load(f)\n\n\ndef load_playbook(name: str, styles_dir: Optional[Path] = None) -> dict[str, Any]:\n    \"\"\"Load and validate a style playbook by name.\n\n    Args:\n        name: Playbook name (without .yaml extension).\n        styles_dir: Override directory for playbook files.\n\n    Returns:\n        Validated playbook dict.\n    \"\"\"\n    styles_dir = styles_dir or STYLES_DIR\n    path = styles_dir / f\"{name}.yaml\"\n    if not path.exists():\n        raise FileNotFoundError(f\"Playbook not found: {path}\")\n\n    with open(path, encoding=\"utf-8\") as f:\n        playbook = yaml.safe_load(f)\n\n    validate_playbook(playbook)\n    return playbook\n\n\ndef validate_playbook(playbook: dict) -> None:\n    \"\"\"Validate a playbook dict against the schema.\"\"\"\n    schema = _load_playbook_schema()\n    jsonschema.validate(instance=playbook, schema=schema)\n\n\ndef list_playbooks(styles_dir: Optional[Path] = None) -> list[str]:\n    \"\"\"List all available playbook names.\"\"\"\n    styles_dir = styles_dir or STYLES_DIR\n    return [","sourceCodeStart":28,"sourceCodeEnd":64,"githubUrl":"https://github.com/calesthio/OpenMontage/blob/95e1c3d0ab93482159818560f6a8c8e866b9139f/styles/playbook_loader.py#L28-L64","documentation":"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.","triggerScenarios":"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/.","commonSituations":"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.","solutions":["List the styles directory (or call the registry/API that enumerates playbooks) and use an exact existing name","If a custom playbook is intended, place <name>.yaml in styles_dir and ensure it passes validate_playbook","Pass the name without the .yaml extension — the loader appends it","For custom locations, pass styles_dir explicitly as an absolute Path"],"exampleFix":"# before\nplaybook = load_playbook(\"corparate-modern\")  # typo\n\n# after\nfrom pathlib import Path\nplaybook = load_playbook(\"corporate-modern\", styles_dir=Path(\"/abs/styles\"))","handlingStrategy":"validation","validationCode":"from pathlib import Path\ndef playbook_path(styles_dir: Path, name: str) -> Path:\n    clean = name.removesuffix(\".yaml\").removesuffix(\".yml\")\n    return styles_dir / f\"{clean}.yaml\"\n\np = playbook_path(STYLES_DIR, name)\nif not p.exists():\n    available = sorted(x.stem for x in STYLES_DIR.glob(\"*.yaml\"))\n    raise SystemExit(f\"unknown playbook {name!r}; available: {available}\")","typeGuard":"def playbook_exists(name: str, styles_dir: Path | None = None) -> bool:\n    d = styles_dir or STYLES_DIR\n    return (d / f\"{name.removesuffix('.yaml')}.yaml\").is_file()","tryCatchPattern":"try:\n    playbook = load_playbook(name)\nexcept FileNotFoundError:\n    raise SystemExit(f\"playbook {name!r} not found — check styles/ for valid names\")","preventionTips":["Enumerate available playbooks from styles/*.yaml and expose them in UX instead of free-text input","Strip .yaml suffixes from user input before loading","Use absolute styles_dir overrides for custom locations"],"tags":["config","file-not-found","yaml","styles"],"backgroundTag":null,"analyzedSha":"95e1c3d0ab93482159818560f6a8c8e866b9139f","analyzedAt":"2026-08-15T06:31:20.014Z","schemaVersion":2},"datasetVersion":"2026-08-15T17:31:12.345Z"}