github/spec-kit · error · TemplateResolutionError

Invalid extension registry {registry}: root must be a mappin

Error message

Invalid extension registry {registry}: root must be a mapping

What it means

_StagedWorkflowFile._write() loops over chunks with os.write(); POSIX write() returning <= 0 without raising is treated as failure ('Failed to write staged workflow file', OSError). For regular files this essentially never happens — a 0 return on a non-zero request indicates a broken fd or an exotic/filesystem-level fault, so the staged install aborts rather than produce a truncated file.

Source

Thrown at scripts/python/common.py:258


def _sorted_extension_ids(extensions_dir: Path) -> list[str]:
    registry = extensions_dir / ".registry"
    registered_ids: set[str] = set()
    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)

View on GitHub (pinned to bf88c9f9a8)

Solutions

  1. Retry the workflow install once — transient filesystem faults usually clear
  2. Move the project (or at least .specify) to a local, POSIX-compliant filesystem (ext4/APFS) and retry
  3. Check df -h and dmesg for ENOSPC or I/O errors at the time of failure
  4. If it reproduces consistently on one machine, capture the fd state (ls -l /proc/<pid>/fd) and report as a filesystem-level bug

Example fix

# before: staging on a flaky network mount
$ specify workflow add ...  # OSError: Failed to write staged workflow file

# after
$ mv project ~/local-disk/ && cd ~/local-disk/project
$ specify workflow add ...  # ok
Defensive patterns

Strategy: retry

Validate before calling

import os

def fd_is_writable(fd: int) -> bool:
    try:
        return os.write(fd, b'') == 0  # zero-length write is a no-op probe
    except OSError:
        return False

Try / catch

try:
    staged.write_bytes(data)
except OSError as e:
    if 'Failed to write staged workflow file' in str(e):
        staged = reopen_staged_file()  # new fd, fresh attempt once
        staged.write_bytes(data)
    else:
        raise

Prevention

When it happens

Trigger: os.write() to the O_NOFOLLOW-opened staged fd returns 0 or negative — possible with a closed/reused fd, a FUSE/network filesystem returning degenerate results under load, or an interrupted/instrumented runtime.

Common situations: Writing the staged workflow onto sshfs/NFS/FUSE mounts with flaky semantics; a bug elsewhere closing the fd prematurely; sandboxed runtimes (seccomp/LSAN-instrumented) altering syscall behavior; disk-full edge cases on exotic filesystems that return 0 instead of ENOSPC.

Related errors


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