github/spec-kit · error · TemplateResolutionError
Failed to read template layer for '{template_name}': {exc}
Error message
Failed to read template layer for '{template_name}': {exc} What it means
While walking the template layer stack, compose_from_base() reads each layer file with read_bytes().decode("utf-8"). Any OSError (missing file, permission denied) or UnicodeError (non-UTF-8 bytes) is converted into TemplateResolutionError with the template name and the underlying exception, so the raw filesystem/encoding failure is never silently ignored.
Source
Thrown at scripts/python/common.py:473
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()
presets_dir = repo_root / ".specify" / "presets"
for preset_id in _sorted_preset_ids(presets_dir):
layer = _preset_template_layer(presets_dir / preset_id, template_name)View on GitHub (pinned to bf88c9f9a8)
Solutions
- Check the underlying exception in the message: for FileNotFoundError, restore the missing layer file (git checkout -- <path>) or remove its manifest entry.
- For a decode error, re-save the layer file as UTF-8 (iconv -f UTF-16 -t UTF-8 or editor 'Save with encoding UTF-8').
- For permission errors, fix ownership/permissions (chmod 644 <layer>, chown) so the running user can read it.
Example fix
# before — layer saved as UTF-16 (UnicodeDecodeError at compose time) # after — convert and re-save # iconv -f UTF-16 -t UTF-8 layer.md > layer.utf8.md && mv layer.utf8.md layer.md
Defensive patterns
Strategy: try-catch
Validate before calling
from pathlib import Path
def layers_are_readable(layers: list[tuple[Path, str]]) -> list[Path]:
bad = []
for path, _ in layers:
try:
path.read_bytes().decode("utf-8")
except (OSError, UnicodeError):
bad.append(path)
return bad
# before composing
if problems := layers_are_readable(layers):
print(f"unreadable layers: {problems}") Try / catch
try:
content = resolve_template_content(name, repo_root)
except TemplateResolutionError as exc:
if "Failed to read template layer" in str(exc):
logger.warning("template %s unavailable (%s); falling back to default", name, exc)
content = None
else:
raise Prevention
- Keep template files owned/readable by the user running the CLI.
- Save all template layers as UTF-8.
- Avoid mutating .specify/templates while a specify command is running.
When it happens
Trigger: A layer path recorded from the preset/override resolution disappears before it is read (race with deletion, broken symlink); a layer file is chmod 000 or owned by another user; a layer saved as latin-1/UTF-16 containing bytes invalid in UTF-8.
Common situations: Concurrent git operations (checkout/rebase) removing a preset file mid-run; templates copied from Windows saved as UTF-16; permission changes after running under a different user (root-created files then run as normal user).
Related errors
- Wrap layer {path} is missing {placeholder}
- Template '{template_name}' has composing layers but no repla
- specify event ${{command}} (${{event}}) failed: ${{(e as Err
- errors.join('; ')
- Error: --short-name requires a value
AI-assisted analysis of github/spec-kit@bf88c9f9a8 (2026-08-14).
Data as JSON: /api/errors/e15f17ae55dfa14e.
Report an issue: GitHub.