fix(presets): treat an unreadable core template as missing (#3961)

* fix(presets): treat an unreadable core template as missing

_substitute_core_template() read the resolved core template with a bare
read_text(), so one corrupted project-owned override in
.specify/templates/commands/ crashed the whole wrap-strategy command
registration with a raw UnicodeDecodeError. Both callers
(CommandRegistrar.register_pack and _register_commands) are unguarded
here, even though register_pack already skips an unreadable preset
source with a warning a few lines above the call.

Treat an unreadable core template like a missing one — warn and return
the body unchanged with empty frontmatter — matching the function's
documented no-core contract.

Assisted-by: GitHub Copilot (model: claude-fable-5, autonomous)
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* test: assert the unreadable-core warning instead of suppressing it

Review follow-up: use pytest.warns so removing or changing the promised
warning fails the test.

Assisted-by: GitHub Copilot (model: claude-fable-5, autonomous)
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

---------

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
This commit is contained in:
Marsel Safin
2026-08-07 12:49:58 -05:00
committed by GitHub
co-authored by Copilot
parent 6da6956c3e
commit 247abbf5e4
2 changed files with 47 additions and 2 deletions
+18 -2
View File
@@ -178,7 +178,7 @@ def _substitute_core_template(
by the core template body and core_frontmatter holds the core template's parsed
frontmatter (so callers can inherit scripts/agent_scripts from it). Both are
unchanged / empty when the placeholder is absent or the core template file does
not exist.
not exist or cannot be read.
"""
if "{CORE_TEMPLATE}" not in body:
return body, {}
@@ -208,7 +208,23 @@ def _substitute_core_template(
if core_file is None:
return body, {}
core_frontmatter, core_body = registrar.parse_frontmatter(core_file.read_text(encoding="utf-8"))
# Treat an unreadable/undecodable core template like a missing one so a
# single corrupted project override cannot crash command registration —
# the wrap-strategy callers already skip an unreadable preset source with
# a warning (CommandRegistrar.register_pack).
try:
core_content = core_file.read_text(encoding="utf-8")
except (OSError, UnicodeDecodeError) as exc:
import warnings
warnings.warn(
f"Ignoring core template for command '{cmd_name}': could not read "
f"'{core_file.name}' ({exc.__class__.__name__}: {exc}).",
stacklevel=2,
)
return body, {}
core_frontmatter, core_body = registrar.parse_frontmatter(core_content)
return body.replace("{CORE_TEMPLATE}", core_body), core_frontmatter