github/spec-kit · error · TemplateResolutionError

Unknown template composition strategy

Error message

Unknown template composition strategy '{strategy}' in {path}

What it means

Template layer composition only understands the strategies prepend, append, wrap, and replace (replace layers become the base). A layer manifest whose strategy string is anything else hits the final else-branch in compose_from_base() and raises TemplateResolutionError naming the unknown strategy and the layer file that declared it.

Solutions

  1. Open the layer file named in the error message, find its manifest, and set strategy to one of: replace, prepend, append, or wrap (lowercase, exact).
  2. Check for trailing whitespace or capitalization in the strategy value (e.g. 'Prepend', 'prepend ').
  3. Upgrade docs awareness: there is no 'merge' or 'concat' strategy — restructure the preset using prepend/append/wrap.

Example fix

# before (manifest.yaml)
strategy: pre-pend
# after
strategy: prepend
Defensive patterns

Strategy: validation

Validate before calling

ALLOWED = {"replace", "prepend", "append", "wrap"}

def strategy_is_valid(value) -> bool:
    return isinstance(value, str) and value in ALLOWED

# validate all manifests before resolution
assert all(strategy_is_valid(m.get("strategy")) for m in loaded_manifests)

Type guard

from typing import Any

ALLOWED = {"replace", "prepend", "append", "wrap"}

def is_strategy(value: Any) -> bool:
    """Narrow a manifest 'strategy' field to a known composition strategy."""
    return isinstance(value, str) and value in ALLOWED

Try / catch

try:
    content = resolve_template_content(name, repo_root)
except TemplateResolutionError as exc:
    if "Unknown template composition strategy" in str(exc):
        raise SystemExit(f"Fix the manifest named in: {exc}") from exc
    raise

Prevention

When it happens

Trigger: A manifest declares strategy: pre-pend, strategy: Wrap (case-sensitive), strategy: merge, or an empty/misspelled value; the layer loads fine but fails only at composition time, so the error message includes the specific path.

Common situations: Typos in hand-written preset manifests; strategy names copied from docs of a different tool version that supported extra strategies; trailing whitespace or a YAML quote issue making the value 'prepend ' with a trailing space.

Related errors


AI-assisted analysis of github/spec-kit@bf88c9f9a8 (2026-08-14). Data as JSON: /api/errors/193022e361037f3b. Report an issue: GitHub.

Appendix: source

Thrown at scripts/python/common.py:469

    def compose_from_base() -> str:
        try:
            content = layers[-1][0].read_bytes().decode("utf-8")
            for path, strategy in reversed(layers[:-1]):
                layer_content = path.read_bytes().decode("utf-8")
                if strategy == "prepend":
                    content = f"{layer_content}\n\n{content}"
                elif strategy == "append":
                    content = f"{content}\n\n{layer_content}"
                elif strategy == "wrap":
                    placeholder = "{CORE_TEMPLATE}"
                    if placeholder not in layer_content:
                        raise TemplateResolutionError(
                            f"Wrap layer {path} is missing {placeholder}"
                        )
                    content = layer_content.replace(placeholder, content)
                else:
                    raise TemplateResolutionError(
                        f"Unknown template composition strategy '{strategy}' in {path}"
                    )
        except (OSError, UnicodeError) as exc:
            raise TemplateResolutionError(
                f"Failed to read template layer for '{template_name}': {exc}"
            ) from exc
        return content

    override = (
        repo_root
        / ".specify"
        / "templates"
        / "overrides"
        / f"{template_name}.md"
    )
    if override.is_file():
        layers.append((override, "replace"))
        return compose_from_base()

View on GitHub (pinned to bf88c9f9a8)