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

  1. Check the registry: inspect .specify/extensions/registry (or run the list-extensions CLI verb) to confirm the exact installed ID.
  2. Install the extension first (specify extension install / add), then call the metadata update.
  3. If the registry is corrupted, restore it from source control or reinstall the extension to rebuild the entry.
  4. 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

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


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