github/spec-kit · error · BundlerError
Catalog entry id mismatch: key '{key}' != entry id '{entry_i
Error message
Catalog entry id mismatch: key '{key}' != entry id '{entry_id}'. What it means
Raised by load_catalog_payload() when an entry's internal 'id' differs from its enclosing bundles-map key. Because the key is what search/resolve/install actually use, a mismatch could let a catalog advertise one id while delivering another bundle's metadata; the strict equality check closes that aliasing vector.
Source
Thrown at src/specify_cli/bundler/models/catalog.py:234
if not isinstance(data, dict):
raise BundlerError("Catalog payload must be a JSON object.")
bundles_raw = data.get("bundles")
if not isinstance(bundles_raw, dict):
raise BundlerError("Catalog payload is missing a 'bundles' object.")
entries: dict[str, CatalogEntry] = {}
for bundle_id, entry_raw in bundles_raw.items():
key = str(bundle_id)
entry = CatalogEntry.from_dict(entry_raw)
# The enclosing key is the authoritative bundle id used by
# search/resolve/install. Reject entries whose own ``id`` is missing or
# disagrees with the key, so a malformed or malicious catalog can't list
# an id that resolves to a different (or no) bundle.
if not entry.id:
raise BundlerError(
f"Catalog entry for '{key}' is missing its 'id' field."
)
if entry.id != key:
raise BundlerError(
f"Catalog entry id mismatch: key '{key}' != entry id "
f"'{entry.id}'."
)
entries[key] = entry
return entries
def load_source_stack(project_root: Path, user_config_dir: Path | None = None) -> list[CatalogSource]:
"""Build the effective, priority-sorted source stack (project > user > built-in).
A source id present at a higher-precedence scope overrides the same id at a
lower scope. The built-in default stack is always the fallback.
"""
by_id: dict[str, CatalogSource] = {}
# Lowest precedence first; later writes override earlier ones for the same id.
for raw in BUILTIN_DEFAULT_STACK:
src = CatalogSource.from_dict(raw, Scope.BUILTIN)View on GitHub (pinned to bf88c9f9a8)
Solutions
- Make the entry's 'id' field byte-for-byte identical to its bundles key, including case and separators.
- Fix generator code so the same variable produces both the key and the id.
- Write a CI lint that asserts entry.id == key for every bundle.
- If aliasing is desired, publish separate entries under each id instead of one mismatched entry.
Example fix
# before (catalog JSON)
{"bundles": {"my-bundle": {"id": "My_Bundle", ...}}}
# after (catalog JSON)
{"bundles": {"my-bundle": {"id": "my-bundle", ...}}} Defensive patterns
Strategy: validation
Validate before calling
for key, raw in payload["bundles"].items():
entry_id = str(raw.get("id", "")).strip()
if entry_id != str(key):
raise ValueError(f"key '{key}' != entry id '{entry_id}'")
load_catalog_payload(payload) Type guard
def entry_id_matches_key(key: str, raw: object) -> bool:
return isinstance(raw, dict) and str(raw.get("id", "")).strip() == str(key) Try / catch
try:
load_catalog_payload(data)
except BundlerError as e:
if "id mismatch" in str(e):
quarantine_catalog(source) # possible aliasing; do not partially trust Prevention
- Generate both the bundles key and the entry 'id' from one variable.
- Never rename one side (key or id) without the other.
- CI lint: assert entry.id == key for every bundle in published catalogs.
When it happens
Trigger: A catalog with {"bundles": {"safe-name": {"id": "evil-name", ...}}}; renaming a key without updating the entry body (or vice versa) during hand edits; generators that slugify keys differently from ids.
Common situations: Copy-paste-then-rename editing of catalogs; key normalization (case-folding, hyphen/underscore swaps) applied only to keys; deliberately aliased catalogs, which are not permitted.
Related errors
- Catalog entry for '{key}' is missing its 'id' field.
- Catalog entry '{entry_id}': 'tags' must be a list of strings
- Catalog entry '{entry_id}': 'verified' must be a boolean (tr
- Each catalog entry must be a mapping.
- Catalog entry '{entry_id or '<unknown>'}': 'requires' must b
AI-assisted analysis of github/spec-kit@bf88c9f9a8 (2026-08-14).
Data as JSON: /api/errors/97716d0af3e28ac8.
Report an issue: GitHub.