github/spec-kit · error · KeyError
Extension '{extension_id}' is not installed
Error message
Extension '{extension_id}' is not installed What it means
ExtensionRegistry.update_extension_metadata() raised KeyError because the given extension_id is not a key in the registry's 'extensions' dict (or the dict itself is missing/not a dict in a corrupted registry). Unlike the ValidationError family, this is a plain KeyError from the registry data layer, used by callers performing enable/disable or other metadata updates on already-installed extensions.
Source
Thrown at src/specify_cli/extensions/__init__.py:855
Merges the provided metadata with the existing entry, preserving any
fields not specified in the new metadata. The installed_at timestamp
is always preserved from the original entry.
Use this method instead of add() when updating existing extension
metadata (e.g., enabling/disabling) to preserve the original
installation timestamp and other existing fields.
Args:
extension_id: Extension ID
metadata: Extension metadata fields to update (merged with existing)
Raises:
KeyError: If extension is not installed
"""
extensions = self.data.get("extensions")
if not isinstance(extensions, dict) or extension_id not in extensions:
raise KeyError(f"Extension '{extension_id}' is not installed")
# Merge new metadata with existing, preserving original installed_at
existing = extensions[extension_id]
# Handle corrupted registry entries (e.g., string/list instead of dict)
if not isinstance(existing, dict):
existing = {}
# Merge: existing fields preserved, new fields override (deep copy to prevent caller mutation)
merged = {**existing, **copy.deepcopy(metadata)}
# Always preserve original installed_at based on key existence, not truthiness,
# to handle cases where the field exists but may be falsy (legacy/corruption)
if "installed_at" in existing:
merged["installed_at"] = existing["installed_at"]
else:
# If not present in existing, explicitly remove from merged if caller provided it
merged.pop("installed_at", None)
extensions[extension_id] = merged
self._save()
def restore(self, extension_id: str, metadata: dict):View on GitHub (pinned to bf88c9f9a8)
Solutions
- Check the registry: inspect .specify/extensions/registry (or run the list-extensions CLI verb) to confirm the exact installed ID.
- Install the extension first (specify extension install / add), then call the metadata update.
- If the registry is corrupted, restore it from source control or reinstall the extension to rebuild the entry.
- Guard the call with a membership check (see validationCode) to fail gracefully for absent IDs.
Example fix
// before
registry.update_extension_metadata("my-ext", {"enabled": True}) # KeyError if absent
// after
if "my-ext" in registry.data.get("extensions", {}):
registry.update_extension_metadata("my-ext", {"enabled": True}) Defensive patterns
Strategy: try-catch
Validate before calling
exts = registry.data.get("extensions")
if not isinstance(exts, dict) or extension_id not in exts:
raise SystemExit(f"extension '{extension_id}' is not installed; run install first")
registry.update_extension_metadata(extension_id, {"enabled": True}) Type guard
def extension_is_installed(registry, extension_id: str) -> bool:
exts = registry.data.get("extensions")
return isinstance(exts, dict) and extension_id in exts Try / catch
try:
registry.update_extension_metadata(extension_id, metadata)
except KeyError:
# extension absent: install it or surface an 'not installed' message to the user
... Prevention
- Check the registry (or CLI list verb) for the exact ID before enable/disable.
- Capture extension IDs from install output and reuse them verbatim.
- Treat KeyError from registry updates as a state bug, not a crash.
When it happens
Trigger: Calling update_extension_metadata('myext', {...}) when 'myext' was never installed, was removed via remove(), or the registry file lost its entry (manual edit, partial uninstall, different .specify directory). The membership check on extensions dict fails and KeyError is raised.
Common situations: Running enable/disable on an extension ID with a typo; operating in the wrong project directory whose registry never had the extension; stale scripts referencing an extension removed by a teammate; registry JSON corrupted so 'extensions' is not a dict.
Related errors
- Cannot restore '{extension_id}': metadata must be a dict
- Preset '{pack_id}' not found in registry
- Invalid extension registry {registry.registry_path}: refusin
- Unsupported agent: {agent_name}
- providers[{i}]: unknown provider {provider!r}; registered: {
AI-assisted analysis of github/spec-kit@bf88c9f9a8 (2026-08-14).
Data as JSON: /api/errors/ada3f49ab61a43e5.
Report an issue: GitHub.