{"record":{"id":"7d79dd19b30f0f40","repo":"github/spec-kit","slug":"preset-pack-id-not-found-in-registry","errorCode":null,"errorMessage":"Preset '{pack_id}' not found in registry","messagePattern":"Preset '(.+?)' not found in registry","errorType":"exception","errorClass":"KeyError","httpStatus":null,"severity":"error","filePath":"src/specify_cli/presets/__init__.py","lineNumber":629,"sourceCode":"            self._save()\n\n    def update(self, pack_id: str, updates: dict):\n        \"\"\"Update preset metadata in registry.\n\n        Merges the provided updates with the existing entry, preserving any\n        fields not specified. The installed_at timestamp is always preserved\n        from the original entry.\n\n        Args:\n            pack_id: Preset ID\n            updates: Partial metadata to merge into existing metadata\n\n        Raises:\n            KeyError: If preset is not installed\n        \"\"\"\n        packs = self.data.get(\"presets\")\n        if not isinstance(packs, dict) or pack_id not in packs:\n            raise KeyError(f\"Preset '{pack_id}' not found in registry\")\n        existing = packs[pack_id]\n        # Handle corrupted registry entries (e.g., string/list instead of dict)\n        if not isinstance(existing, dict):\n            existing = {}\n        # Merge: existing fields preserved, new fields override (deep copy to prevent caller mutation)\n        merged = {**existing, **copy.deepcopy(updates)}\n        # Always preserve original installed_at based on key existence, not truthiness,\n        # to handle cases where the field exists but may be falsy (legacy/corruption)\n        if \"installed_at\" in existing:\n            merged[\"installed_at\"] = existing[\"installed_at\"]\n        else:\n            # If not present in existing, explicitly remove from merged if caller provided it\n            merged.pop(\"installed_at\", None)\n        packs[pack_id] = merged\n        self._save()\n\n    def restore(self, pack_id: str, metadata: dict):\n        \"\"\"Restore preset metadata to registry without modifying timestamps.","sourceCodeStart":611,"sourceCodeEnd":647,"githubUrl":"https://github.com/github/spec-kit/blob/bf88c9f9a82fa370c7a7257aa2b3cf10b457b65c/src/specify_cli/presets/__init__.py#L611-L647","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","solutions":["Check installation first: `registry.is_installed(pack_id)` or `specify preset list` before updating metadata.","If the registry file is corrupted, restore .specify/presets/registry.json from version control or recreate it by reinstalling presets.","Wrap programmatic update_metadata calls in try/except KeyError if the preset's presence is not guaranteed."],"exampleFix":"# before\nregistry.update_metadata(pack_id, {\"last_used\": now})\n\n# after\nif not registry.is_installed(pack_id):\n    raise RuntimeError(f\"cannot update metadata: {pack_id} not installed\")\nregistry.update_metadata(pack_id, {\"last_used\": now})","handlingStrategy":"type-guard","validationCode":"if not registry.is_installed(pack_id):\n    raise RuntimeError(f\"preset {pack_id} not installed; cannot update metadata\")\nregistry.update_metadata(pack_id, updates)","typeGuard":"def can_update(registry, pack_id: str) -> bool:\n    packs = registry.data.get(\"presets\")\n    return isinstance(packs, dict) and pack_id in packs","tryCatchPattern":"try:\n    registry.update_metadata(pack_id, updates)\nexcept KeyError:\n    # preset not installed (or registry corrupted): reinstall then retry\n    ...","preventionTips":["Always gate update_metadata with is_installed().","Keep .specify/presets/registry.json in version control so corruption is recoverable.","Treat a missing entry after 'preset remove' as expected, not exceptional."],"tags":["preset","registry","keyerror","state"],"backgroundTag":null,"analyzedSha":"bf88c9f9a82fa370c7a7257aa2b3cf10b457b65c","analyzedAt":"2026-08-14T19:43:37.150Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}