{"record":{"id":"812da7002da7ace7","repo":"github/spec-kit","slug":"invalid-extension-registry-registry-root-must-b","errorCode":null,"errorMessage":"Invalid extension registry {registry}: root must be a mapping","messagePattern":"Invalid extension registry (.+?): root must be a mapping","errorType":"exception","errorClass":"TemplateResolutionError","httpStatus":null,"severity":"error","filePath":"scripts/python/common.py","lineNumber":258,"sourceCode":"\n\ndef _sorted_extension_ids(extensions_dir: Path) -> list[str]:\n    registry = extensions_dir / \".registry\"\n    registered_ids: set[str] = set()\n    extensions: dict[object, object] = {}\n    if os.path.lexists(registry):\n        if not registry.is_file():\n            raise TemplateResolutionError(\n                f\"Invalid extension registry {registry}: not a regular file\"\n            )\n        try:\n            data = json.loads(registry.read_text(encoding=\"utf-8\"))\n        except (OSError, UnicodeError, json.JSONDecodeError) as exc:\n            raise TemplateResolutionError(\n                f\"Failed to parse extension registry {registry}: {exc}\"\n            ) from exc\n        if not isinstance(data, dict):\n            raise TemplateResolutionError(\n                f\"Invalid extension registry {registry}: root must be a mapping\"\n            )\n        raw_extensions = data.get(\"extensions\", {})\n        if not isinstance(raw_extensions, dict):\n            raise TemplateResolutionError(\n                f\"Invalid extension registry {registry}: \"\n                \"'extensions' must be a mapping\"\n            )\n        extensions = raw_extensions\n        registered_ids = {\n            ext_id for ext_id in extensions if isinstance(ext_id, str)\n        }\n\n    ranked: list[tuple[int, str]] = []\n    for ext_id, metadata in extensions.items():\n        if (\n            _is_safe_component(ext_id)\n            and isinstance(metadata, dict)","sourceCodeStart":240,"sourceCodeEnd":276,"githubUrl":"https://github.com/github/spec-kit/blob/bf88c9f9a82fa370c7a7257aa2b3cf10b457b65c/scripts/python/common.py#L240-L276","documentation":"_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.","triggerScenarios":"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.","commonSituations":"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.","solutions":["Retry the workflow install once — transient filesystem faults usually clear","Move the project (or at least .specify) to a local, POSIX-compliant filesystem (ext4/APFS) and retry","Check df -h and dmesg for ENOSPC or I/O errors at the time of failure","If it reproduces consistently on one machine, capture the fd state (ls -l /proc/<pid>/fd) and report as a filesystem-level bug"],"exampleFix":"# before: staging on a flaky network mount\n$ specify workflow add ...  # OSError: Failed to write staged workflow file\n\n# after\n$ mv project ~/local-disk/ && cd ~/local-disk/project\n$ specify workflow add ...  # ok","handlingStrategy":"retry","validationCode":"import os\n\ndef fd_is_writable(fd: int) -> bool:\n    try:\n        return os.write(fd, b'') == 0  # zero-length write is a no-op probe\n    except OSError:\n        return False","typeGuard":null,"tryCatchPattern":"try:\n    staged.write_bytes(data)\nexcept OSError as e:\n    if 'Failed to write staged workflow file' in str(e):\n        staged = reopen_staged_file()  # new fd, fresh attempt once\n        staged.write_bytes(data)\n    else:\n        raise","preventionTips":["Run installs on a local POSIX filesystem, not FUSE/network mounts","Monitor df -h/dmesg for ENOSPC and I/O errors on build machines","Retry once before investigating — degenerate write() results are usually transient"],"tags":["filesystem","workflow","staging","low-level"],"backgroundTag":null,"analyzedSha":"bf88c9f9a82fa370c7a7257aa2b3cf10b457b65c","analyzedAt":"2026-08-14T19:43:37.150Z","schemaVersion":2},"datasetVersion":"2026-08-15T17:31:12.345Z"}