{"record":{"id":"f18b715b8fca41cc","repo":"github/spec-kit","slug":"each-installed-bundle-record-must-be-a-mapping","errorCode":null,"errorMessage":"Each installed-bundle record must be a mapping.","messagePattern":"Each installed-bundle record must be a mapping\\.","errorType":"exception","errorClass":"BundlerError","httpStatus":null,"severity":"error","filePath":"src/specify_cli/bundler/models/records.py","lineNumber":57,"sourceCode":"            version=version,\n            contributed_components=tuple(components),\n            installed_at=installed_at or _utc_now(),\n        )\n\n    def to_dict(self) -> dict[str, Any]:\n        return {\n            \"bundle_id\": self.bundle_id,\n            \"version\": self.version,\n            \"installed_at\": self.installed_at,\n            \"contributed_components\": [\n                _component_to_dict(c) for c in self.contributed_components\n            ],\n        }\n\n    @classmethod\n    def from_dict(cls, data: Any) -> \"InstalledBundleRecord\":\n        if not isinstance(data, dict):\n            raise BundlerError(\"Each installed-bundle record must be a mapping.\")\n        components_raw = data.get(\"contributed_components\")\n        if components_raw is None:\n            components_raw = []\n        elif not isinstance(components_raw, list):\n            # `or []` would coerce a FALSY non-list (0, '', False, {}) to []\n            # before this guard, silently accepting a corrupt record; only an\n            # absent/None value means \"no components\".\n            raise BundlerError(\n                \"Corrupt record: 'contributed_components' must be a list.\"\n            )\n        bundle_id = str(data.get(\"bundle_id\", \"\")).strip()\n        version = str(data.get(\"version\", \"\")).strip()\n        if not bundle_id:\n            raise BundlerError(\n                \"Corrupt records file: an installed-bundle record is missing \"\n                \"its 'bundle_id'.\"\n            )\n        if not version:","sourceCodeStart":39,"sourceCodeEnd":75,"githubUrl":"https://github.com/github/spec-kit/blob/bf88c9f9a82fa370c7a7257aa2b3cf10b457b65c/src/specify_cli/bundler/models/records.py#L39-L75","documentation":"Raised by InstalledBundleRecord.from_dict when an entry in the installed-bundles records file (.specify/… installed-bundles JSON) is not a JSON object. Each record must be a mapping with bundle_id, version, installed_at, and contributed_components; list or scalar entries indicate corruption.","triggerScenarios":"load_records reads the records file, iterates its 'bundles' list, and calls InstalledBundleRecord.from_dict(item) — an item that is a string, number, or array trips the isinstance(data, dict) guard.","commonSituations":"Hand-editing the records JSON and turning a record into a bare bundle-id string; a partially written file after a crash; a merge conflict resolved incorrectly.","solutions":["Restore the entry to an object with bundle_id, version, installed_at, contributed_components.","If the record's bundle is gone, remove the whole entry from 'bundles' rather than deforming it.","Regenerate state by reinstalling the bundle after deleting the corrupt record."],"exampleFix":"// before (installed-bundles.json)\n\"bundles\": [\"my-bundle\"]\n\n// after\n\"bundles\": [\n  {\n    \"bundle_id\": \"my-bundle\",\n    \"version\": \"1.0.0\",\n    \"installed_at\": \"2026-01-01T00:00:00Z\",\n    \"contributed_components\": []\n  }\n]","handlingStrategy":"validation","validationCode":"import json\nfrom pathlib import Path\n\ndef records_entries_are_objects(path: Path) -> bool:\n    data = json.loads(path.read_text())\n    return all(isinstance(b, dict) for b in data.get(\"bundles\", []))","typeGuard":"def is_record_mapping(item: object) -> bool:\n    return isinstance(item, dict)","tryCatchPattern":"from specify_cli.bundler.models.records import load_records, BundlerError\ntry:\n    records = load_records(project_root)\nexcept BundlerError as e:\n    if \"must be a mapping\" in str(e):\n        # quarantine the file, reinstall bundles to rebuild state\n        ...","preventionTips":["Never hand-edit the installed-bundles records file; use bundle install/uninstall.","Back up .specify state before bulk edits.","Treat records as tool-owned derived state, not source."],"tags":["bundler","records","json","validation","corruption"],"backgroundTag":null,"analyzedSha":"bf88c9f9a82fa370c7a7257aa2b3cf10b457b65c","analyzedAt":"2026-08-14T19:43:37.150Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}