github/spec-kit · error · BundlerError

Corrupt record: 'contributed_components' must be a list.

Error message

Corrupt record: 'contributed_components' must be a list.

What it means

Raised by InstalledBundleRecord.from_dict when a record's 'contributed_components' key is present but is not a JSON array. The comment documents why: a naive 'or []' would coerce falsy non-lists (0, '', false, {}) to empty and silently accept a corrupt record; only absent/null means 'no components'.

Source

Thrown at src/specify_cli/bundler/models/records.py:65

            "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:
            raise BundlerError(
                f"Corrupt records file: record for bundle '{bundle_id}' is "
                "missing its 'version'."
            )
        return cls(
            bundle_id=bundle_id,
            version=version,
            installed_at=str(data.get("installed_at", "")).strip(),

View on GitHub (pinned to bf88c9f9a8)

Solutions

  1. Set 'contributed_components' to an array of component objects (or [] if none).
  2. Remove the key entirely if there are no contributed components — absence is valid.

Example fix

// before
"contributed_components": "my-skill"

// after
"contributed_components": [
  {"kind": "skill", "id": "my-skill", "source": "bundle"}
]
Defensive patterns

Strategy: validation

Validate before calling

def components_key_ok(record: dict) -> bool:
    c = record.get("contributed_components")
    return c is None or isinstance(c, list)

Type guard

def is_optional_component_list(raw: object) -> bool:
    return raw is None or isinstance(raw, list)

Try / catch

try:
    records = load_records(project_root)
except BundlerError as e:
    if "'contributed_components' must be a list" in str(e):
        # fix the record or delete it and reinstall
        ...

Prevention

When it happens

Trigger: A records-file entry has "contributed_components": "skills" (string), 0, false, or {} instead of an array. from_dict rejects it before building component tuples.

Common situations: Hand-editing the records file to note a component kind as a string; schema drift from older tooling; truncated writes.

Related errors


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