github/spec-kit · error · TemplateResolutionError
Invalid extension registry {registry}: 'extensions' must be
Error message
Invalid extension registry {registry}: 'extensions' must be a mapping What it means
_StagedWorkflowFile.verify_path() stats the staged path with follow_symlinks=False and fstats the open fd; if either stat call raises OSError, it wraps it as 'Staged workflow file changed before commit'. At this point the staged file was just written, so a failing stat means the path vanished or became inaccessible between write and commit — evidence of tampering or filesystem trouble — and the atomic commit is aborted.
Source
Thrown at scripts/python/common.py:263
extensions: dict[object, object] = {}
if os.path.lexists(registry):
if not registry.is_file():
raise TemplateResolutionError(
f"Invalid extension registry {registry}: not a regular file"
)
try:
data = json.loads(registry.read_text(encoding="utf-8"))
except (OSError, UnicodeError, json.JSONDecodeError) as exc:
raise TemplateResolutionError(
f"Failed to parse extension registry {registry}: {exc}"
) from exc
if not isinstance(data, dict):
raise TemplateResolutionError(
f"Invalid extension registry {registry}: root must be a mapping"
)
raw_extensions = data.get("extensions", {})
if not isinstance(raw_extensions, dict):
raise TemplateResolutionError(
f"Invalid extension registry {registry}: "
"'extensions' must be a mapping"
)
extensions = raw_extensions
registered_ids = {
ext_id for ext_id in extensions if isinstance(ext_id, str)
}
ranked: list[tuple[int, str]] = []
for ext_id, metadata in extensions.items():
if (
_is_safe_component(ext_id)
and isinstance(metadata, dict)
and bool(metadata.get("enabled", True))
):
ranked.append((_normalize_priority(metadata.get("priority")), ext_id))
try:View on GitHub (pinned to bf88c9f9a8)
Solutions
- Ensure only one specify/workflow process runs against the project at a time (lock or serialize CI steps)
- Exclude .specify (and the staging dir) from tmp-reaper/antivirus/cleaner tooling and `git clean -fdx`
- Retry the install once after the race source is removed
- If it persists, strace/fs_usage the run to find which process unlinks the staged path
Example fix
# before # CI: two jobs run specify simultaneously in one workspace # after # serialize the steps - run: specify workflow add my.yaml - run: specify build # after, not parallel
Defensive patterns
Strategy: retry
Validate before calling
import os, stat
def staged_path_still_valid(staged_path: os.PathLike, fd: int) -> bool:
try:
ps = os.stat(staged_path, follow_symlinks=False)
fs = os.fstat(fd)
except OSError:
return False
return stat.S_ISREG(ps.st_mode) and (ps.st_dev, ps.st_ino) == (fs.st_dev, fs.st_ino) Try / catch
try:
staged.verify_path()
except OSError as e:
if 'changed before commit' in str(e):
restage_and_retry_once() # reopen a fresh staged file and rewrite
else:
raise Prevention
- Serialize specify/workflow processes per project (locks, sequential CI steps)
- Exclude .specify and staging dirs from tmp janitors, antivirus, and git clean -fdx
- Retry once after removing the racing cleanup source
When it happens
Trigger: Something removes or re-permissions the staged file (typically under .specify/tmp or the staging dir) between os.write and verify: concurrent cleanup (another specify run, tmp reaper, `git clean`), antivirus/SED quarantining freshly written files, or a flaky mount dropping the inode.
Common situations: Parallel `specify` invocations racing in the same repo; tmp-file reapers or CI janitors deleting new files in .specify/tmp; antivirus on macOS/Windows delaying or removing just-written executables; interrupted runs leaving cleanup handlers that fire late.
Related errors
- Invalid extension registry {registry}: root must be a mappin
- PyYAML is required to resolve preset template composition
- Error: branch_template must include the {number} token so ge
- Error: branch_template must not place {slug} before {number}
- Error: branch_template must put {number}- at the start of th
AI-assisted analysis of github/spec-kit@bf88c9f9a8 (2026-08-14).
Data as JSON: /api/errors/dc8e1825495b415c.
Report an issue: GitHub.