github/spec-kit · error · BundlerError
Each installed-bundle record must be a mapping.
Error message
Each installed-bundle record must be a mapping.
What it means
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.
Source
Thrown at src/specify_cli/bundler/models/records.py:57
version=version,
contributed_components=tuple(components),
installed_at=installed_at or _utc_now(),
)
def to_dict(self) -> dict[str, Any]:
return {
"bundle_id": self.bundle_id,
"version": self.version,
"installed_at": self.installed_at,
"contributed_components": [
_component_to_dict(c) for c in self.contributed_components
],
}
@classmethod
def from_dict(cls, data: Any) -> "InstalledBundleRecord":
if not isinstance(data, dict):
raise BundlerError("Each installed-bundle record must be a mapping.")
components_raw = data.get("contributed_components")
if components_raw is None:
components_raw = []
elif not isinstance(components_raw, list):
# `or []` would coerce a FALSY non-list (0, '', False, {}) to []
# before this guard, silently accepting a corrupt record; only an
# absent/None value means "no components".
raise BundlerError(
"Corrupt record: 'contributed_components' must be a list."
)
bundle_id = str(data.get("bundle_id", "")).strip()
version = str(data.get("version", "")).strip()
if not bundle_id:
raise BundlerError(
"Corrupt records file: an installed-bundle record is missing "
"its 'bundle_id'."
)
if not version:View on GitHub (pinned to bf88c9f9a8)
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.
Example fix
// before (installed-bundles.json)
"bundles": ["my-bundle"]
// after
"bundles": [
{
"bundle_id": "my-bundle",
"version": "1.0.0",
"installed_at": "2026-01-01T00:00:00Z",
"contributed_components": []
}
] Defensive patterns
Strategy: validation
Validate before calling
import json
from pathlib import Path
def records_entries_are_objects(path: Path) -> bool:
data = json.loads(path.read_text())
return all(isinstance(b, dict) for b in data.get("bundles", [])) Type guard
def is_record_mapping(item: object) -> bool:
return isinstance(item, dict) Try / catch
from specify_cli.bundler.models.records import load_records, BundlerError
try:
records = load_records(project_root)
except BundlerError as e:
if "must be a mapping" in str(e):
# quarantine the file, reinstall bundles to rebuild state
... Prevention
- 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.
When it happens
Trigger: 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.
Common situations: 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.
Related errors
- Corrupt record: 'contributed_components' must be a list.
- Corrupt records file: an installed-bundle record is missing
- Corrupt records file: record for bundle '{bundle_id}' is mis
- Corrupt records file: {path}
- Corrupt records file: {path} — missing 'schema_version'. Exp
AI-assisted analysis of github/spec-kit@bf88c9f9a8 (2026-08-14).
Data as JSON: /api/errors/f18b715b8fca41cc.
Report an issue: GitHub.