github/spec-kit · error · BundlerError

Corrupt records file: {path}

Error message

Corrupt records file: {path}

What it means

Raised by load_records when the installed-bundles records file parses to a non-object JSON value at the top level (e.g. an array or bare string). The reader needs a mapping to extract 'schema_version' and 'bundles'; anything else is treated as corruption with the path in the message. Note the read path is also confined via ensure_within, refusing symlinked or traversal-escaping .specify paths.

Source

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

    seen = str(value).strip()
    if seen.split(".")[0] != RECORDS_SCHEMA_VERSION.split(".")[0]:
        raise BundlerError(
            f"Unsupported records schema version '{seen}' at {path}; this "
            f"Spec Kit understands version {RECORDS_SCHEMA_VERSION}. The file may "
            "have been written by a newer version or is corrupt."
        )


def load_records(project_root: Path) -> list[InstalledBundleRecord]:
    # Defense in depth (mirrors the write path's within= confinement): refuse to
    # read through a symlinked or traversal-escaping ``.specify`` that resolves
    # outside project_root.
    path = ensure_within(project_root, records_path(project_root))
    if not path.exists():
        return []
    data = load_json(path)
    if not isinstance(data, dict):
        raise BundlerError(f"Corrupt records file: {path}")
    _check_schema_version(data.get("schema_version"), path=path, required=True)
    bundles = data.get("bundles")
    if bundles is None:
        bundles = []
    elif not isinstance(bundles, list):
        # `or []` would coerce a FALSY non-list (0, '', False, {}) to [] before
        # this guard, silently treating a corrupt file as "no bundles"; only an
        # absent/None value means empty.
        raise BundlerError(
            f"Corrupt records file: {path} — 'bundles' must be a list."
        )
    return [InstalledBundleRecord.from_dict(item) for item in bundles]


def save_records(project_root: Path, records: list[InstalledBundleRecord]) -> None:
    payload = {
        "schema_version": RECORDS_SCHEMA_VERSION,
        "updated_at": _utc_now(),

View on GitHub (pinned to bf88c9f9a8)

Solutions

  1. Wrap the content in a top-level object: {"schema_version": "1", "bundles": [...]}.
  2. If content integrity is doubtful, delete the file and reinstall bundles — load_records returns [] for a missing file.
  3. Check for symlinks under .specify pointing outside the project if the path in the message looks unexpected.

Example fix

// before (installed-bundles.json)
[
  {"bundle_id": "my-bundle", "version": "1.0.0"}
]

// after
{
  "schema_version": "1",
  "bundles": [
    {"bundle_id": "my-bundle", "version": "1.0.0"}
  ]
}
Defensive patterns

Strategy: validation

Validate before calling

import json
from pathlib import Path

def records_is_object(path: Path) -> bool:
    return isinstance(json.loads(path.read_text()), dict)

Type guard

def is_records_object(data: object) -> bool:
    return isinstance(data, dict)

Try / catch

try:
    records = load_records(project_root)
except BundlerError as e:
    if "Corrupt records file" in str(e):
        # back up the file, then either repair to {schema_version, bundles} or
        # delete it and reinstall bundles to rebuild state
        ...

Prevention

When it happens

Trigger: load_records(project_root) finds the records path exists, load_json returns a list/scalar, and the isinstance(data, dict) guard raises before schema-version checking.

Common situations: A records file overwritten with a JSON array of records (dropping the wrapper object); truncation/corruption after a crash; editing in a tool that reformatted the whole document.

Related errors


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