github/spec-kit · error · KeyError

Preset '{pack_id}' not found in registry

Error message

Preset '{pack_id}' not found in registry

What it means

PresetRegistry.update_metadata raises KeyError when the given pack_id is not a key under the registry's `presets` mapping, or when the registry's `presets` field is not a dict at all (corrupted registry file). It is a programming-contract error: update only works on already-installed presets.

Source

Thrown at src/specify_cli/presets/__init__.py:629

            self._save()

    def update(self, pack_id: str, updates: dict):
        """Update preset metadata in registry.

        Merges the provided updates with the existing entry, preserving any
        fields not specified. The installed_at timestamp is always preserved
        from the original entry.

        Args:
            pack_id: Preset ID
            updates: Partial metadata to merge into existing metadata

        Raises:
            KeyError: If preset is not installed
        """
        packs = self.data.get("presets")
        if not isinstance(packs, dict) or pack_id not in packs:
            raise KeyError(f"Preset '{pack_id}' not found in registry")
        existing = packs[pack_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(updates)}
        # 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)
        packs[pack_id] = merged
        self._save()

    def restore(self, pack_id: str, metadata: dict):
        """Restore preset metadata to registry without modifying timestamps.

View on GitHub (pinned to bf88c9f9a8)

Solutions

  1. Check installation first: `registry.is_installed(pack_id)` or `specify preset list` before updating metadata.
  2. If the registry file is corrupted, restore .specify/presets/registry.json from version control or recreate it by reinstalling presets.
  3. Wrap programmatic update_metadata calls in try/except KeyError if the preset's presence is not guaranteed.

Example fix

# before
registry.update_metadata(pack_id, {"last_used": now})

# after
if not registry.is_installed(pack_id):
    raise RuntimeError(f"cannot update metadata: {pack_id} not installed")
registry.update_metadata(pack_id, {"last_used": now})
Defensive patterns

Strategy: type-guard

Validate before calling

if not registry.is_installed(pack_id):
    raise RuntimeError(f"preset {pack_id} not installed; cannot update metadata")
registry.update_metadata(pack_id, updates)

Type guard

def can_update(registry, pack_id: str) -> bool:
    packs = registry.data.get("presets")
    return isinstance(packs, dict) and pack_id in packs

Try / catch

try:
    registry.update_metadata(pack_id, updates)
except KeyError:
    # preset not installed (or registry corrupted): reinstall then retry
    ...

Prevention

When it happens

Trigger: Calling PresetRegistry.update_metadata('some-id', {...}) where 'some-id' was never installed, was removed, or the registry JSON at .specify/presets/registry.json has `presets` as a string/list instead of an object.

Common situations: Calling update right after a failed install (so the entry was never written), using a stale pack_id after `specify preset remove`, or a hand-edited/corrupted registry.json.

Related errors


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