github/spec-kit · error · TemplateResolutionError

Template '{template_name}' has composing layers but no repla

Error message

Template '{template_name}' has composing layers but no replace base

What it means

Layer composition requires exactly one base layer with strategy "replace" (an explicit replace candidate or the core template at .specify/templates/<name>.md) onto which prepend/append/wrap layers are applied. If only composing layers were found — no override file, no replace preset layer, and no core template — the function raises TemplateResolutionError instead of returning partial content.

Source

Thrown at scripts/python/common.py:513

                return compose_from_base()

    extensions_dir = repo_root / ".specify" / "extensions"
    for extension_id in _sorted_extension_ids(extensions_dir):
        extension_dir = extensions_dir / extension_id
        candidate = _conventional_template(extension_dir, template_name)
        if candidate is not None:
            layers.append((candidate, "replace"))
            return compose_from_base()

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

    if not layers:
        return None

    raise TemplateResolutionError(
        f"Template '{template_name}' has composing layers but no replace base"
    )


def get_invoke_separator(repo_root: Path) -> str:
    integration_json = repo_root / ".specify" / "integration.json"
    if not integration_json.is_file():
        return "."
    # Split the parse out of the lookup and guard the top-level shape, matching
    # read_feature_json_feature_directory above and the bash/PowerShell twins,
    # which both fall back to "." for any unusable integration.json:
    #   * a non-mapping top level ([], "forge", 42, null) is valid JSON, so
    #     json.JSONDecodeError never fires and state.get(...) raised
    #     AttributeError;
    #   * a non-UTF-8 file raises UnicodeDecodeError, which is a ValueError --
    #     not an OSError -- so it escaped the except tuple. Realistic on
    #     Windows, where PowerShell 5.1's Out-File/`>` default to UTF-16.
    try:

View on GitHub (pinned to bf88c9f9a8)

Solutions

  1. Restore or create the base template at .specify/templates/<template_name>.md (exact name from the error message).
  2. Alternatively add a layer with strategy: replace in the preset so the stack has a base.
  3. Check filename casing/spelling of both the requested template name and the files on disk.

Example fix

# before — only .specify/templates/presets/team/clarify.md (strategy: prepend), no base
# after — also restore the base file
# git checkout HEAD -- .specify/templates/clarify.md
Defensive patterns

Strategy: validation

Validate before calling

from pathlib import Path

def has_replace_base(repo_root: Path, template_name: str, replace_layers: list[Path]) -> bool:
    if replace_layers:
        return True
    return (repo_root / ".specify" / "templates" / f"{template_name}.md").is_file()

# guard before enabling prepend/append/wrap presets
assert has_replace_base(repo_root, name, replace_layers), "no replace base for layers"

Try / catch

try:
    content = resolve_template_content(name, repo_root)
except TemplateResolutionError as exc:
    if "no replace base" in str(exc):
        raise SystemExit(f"Restore the base template for {exc}") from exc
    raise

Prevention

When it happens

Trigger: A user creates .specify/templates/presets/<p>/<name>.md with strategy prepend/append/wrap but deletes or never creates the core .specify/templates/<name>.md and provides no replace layer; or the core template filename casing mismatches the requested template_name.

Common situations: Removing a stock template thinking presets alone are enough; renaming a template file but not the preset references; a typo in template_name so the core file lookup misses while preset layers with looser matching still hit.

Related errors


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