{"record":{"id":"e7a1a84d9da553ba","repo":"github/spec-kit","slug":"failed-to-parse-preset-manifest-manifest-path","errorCode":null,"errorMessage":"Failed to parse preset manifest {manifest_path}: {exc}","messagePattern":"Failed to parse preset manifest (.+?): (.+?)","errorType":"exception","errorClass":"TemplateResolutionError","httpStatus":null,"severity":"error","filePath":"scripts/python/common.py","lineNumber":438,"sourceCode":"                    entry.get(\"name\") != template_name\n                    or entry.get(\"type\", \"template\") != \"template\"\n                ):\n                    continue\n                file_value = entry.get(\"file\", \"\")\n                strategy = entry.get(\"strategy\", \"replace\")\n                relative = Path(file_value)\n                if (\n                    not relative\n                    or relative.is_absolute()\n                    or \"..\" in relative.parts\n                ):\n                    return None\n                candidate = preset_dir / relative\n                if not candidate.is_file():\n                    return None\n                return candidate, strategy.lower()\n        except (OSError, UnicodeError, ValueError, yaml.YAMLError) as exc:\n            raise TemplateResolutionError(\n                f\"Failed to parse preset manifest {manifest_path}: {exc}\"\n            ) from exc\n\n    return (conventional, \"replace\") if conventional is not None else None\n\n\ndef resolve_template_content(template_name: str, repo_root: Path) -> str | None:\n    \"\"\"Resolve and compose template content through the project layer stack.\"\"\"\n    if not _is_safe_component(template_name):\n        return None\n\n    layers: list[tuple[Path, str]] = []\n\n    def compose_from_base() -> str:\n        try:\n            content = layers[-1][0].read_bytes().decode(\"utf-8\")\n            for path, strategy in reversed(layers[:-1]):\n                layer_content = path.read_bytes().decode(\"utf-8\")","sourceCodeStart":420,"sourceCodeEnd":456,"githubUrl":"https://github.com/github/spec-kit/blob/bf88c9f9a82fa370c7a7257aa2b3cf10b457b65c/scripts/python/common.py#L420-L456","documentation":"Raised while resolving a template preset: the preset's manifest file (YAML) under the preset directory could not be read or parsed. The loader catches OSError, UnicodeError, ValueError, and yaml.YAMLError and re-raises them as TemplateResolutionError with the offending manifest path, so any unreadable, non-UTF-8, or syntactically invalid manifest surfaces here.","triggerScenarios":"A preset directory (e.g. .specify/templates/presets/<name>/) contains a manifest.yaml with a YAML syntax error (bad indentation, unclosed quote, tabs), is saved in a non-UTF-8 encoding, or has file permissions that make read() raise OSError. Occurs whenever resolve_template_content() walks the preset layer stack and hits that manifest.","commonSituations":"Hand-editing a preset manifest and breaking YAML syntax; copying a preset from Windows with a BOM/latin-1 encoding; a manifest deleted or chmod'd between directory listing and read; CI checkout with mangled line endings producing an unparseable scalar.","solutions":["Run python -c \"import yaml,sys; yaml.safe_load(open(sys.argv[1]))\" <manifest_path> to get the exact YAML error location and fix it.","Confirm the file is valid UTF-8 (file <manifest> / re-save as UTF-8 without BOM).","Check file permissions/readability of the preset directory if the message shows an OSError instead of a parser error.","If the preset is not yours, delete or move the broken preset directory out of the preset stack so resolution skips it."],"exampleFix":"# before (manifest.yaml — tab indentation breaks YAML)\nstrategy:\tprepend\n# after\nstrategy: prepend","handlingStrategy":"validation","validationCode":"import yaml\nfrom pathlib import Path\n\ndef manifest_is_loadable(path: Path) -> bool:\n    try:\n        raw = path.read_bytes().decode(\"utf-8\")\n        value = yaml.safe_load(raw)\n        return isinstance(value, dict)\n    except (OSError, UnicodeError, ValueError, yaml.YAMLError):\n        return False\n\n# skip broken presets before calling resolve_template_content\nfor m in preset_dir.glob(\"*/manifest.yaml\"):\n    if not manifest_is_loadable(m):\n        print(f\"skipping unreadable manifest: {m}\")","typeGuard":null,"tryCatchPattern":"try:\n    content = resolve_template_content(name, repo_root)\nexcept TemplateResolutionError as exc:\n    if \"Failed to parse preset manifest\" in str(exc):\n        logger.warning(\"skipping template %s: bad preset manifest (%s)\", name, exc)\n        content = None\n    else:\n        raise","preventionTips":["Lint every preset manifest in CI with yaml.safe_load plus a schema check on strategy values.","Keep preset files UTF-8 encoded without BOM.","Add a unit test that loads all shipped manifests so a syntax error fails the build, not the user."],"tags":["yaml","templates","configuration","preset"],"backgroundTag":null,"analyzedSha":"bf88c9f9a82fa370c7a7257aa2b3cf10b457b65c","analyzedAt":"2026-08-14T19:43:37.150Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}