langflow-ai/langflow · error · SystemExit

error: {source} entries field must be a list

Error message

error: {source} entries field must be a list

What it means

This error comes from scripts/migrate/check_migration_append_only.py, a CI guard that compares the working-tree migration_table.json against a baseline to enforce that the extension migration table is append-only. After parsing the JSON and confirming the top-level value is an object, it reads data['entries'] and requires it to be a Python list, because each entry maps a legacy component form to its new ext:<bundle> target. If 'entries' is present but not a list (a dict, string, number, or null), the script prints 'error: {source} entries field must be a list' to stderr and exits with code 2 (usage / I/O error).

Source

Thrown at scripts/migrate/check_migration_append_only.py:100

def _parse(raw: str, *, source: str) -> tuple[list[dict], list[dict]]:
    """Return ``(entries, ambiguous_bare_names)`` from a migration-table JSON.

    Both lists default to empty when the field is absent so this script can
    compare across baselines that pre-date a given field.
    """
    try:
        data = json.loads(raw)
    except json.JSONDecodeError as exc:
        print(f"error: invalid JSON in {source}: {exc}", file=sys.stderr)
        raise SystemExit(2) from exc
    if not isinstance(data, dict):
        print(f"error: {source} top-level value must be an object", file=sys.stderr)
        raise SystemExit(2)
    entries = data.get("entries", [])
    if not isinstance(entries, list):
        print(f"error: {source} entries field must be a list", file=sys.stderr)
        raise SystemExit(2)
    ambig = data.get("ambiguous_bare_names", [])
    if not isinstance(ambig, list):
        print(f"error: {source} ambiguous_bare_names field must be a list", file=sys.stderr)
        raise SystemExit(2)
    return entries, ambig


def _compare(baseline: list[dict], current: list[dict]) -> list[str]:
    """Return human-readable violations; empty list means clean."""
    violations: list[str] = []
    current_by_key: dict[tuple[str, str], dict] = {}
    for entry in current:
        try:
            key = _entry_key(entry)
        except ValueError as exc:
            violations.append(str(exc))
            continue
        if key in current_by_key:

View on GitHub (pinned to 976ec789d2)

Solutions

  1. Open the file named in the error message and change the 'entries' value to a JSON array of entry objects: "entries": [ { ... }, ... ].
  2. If you intentionally restructured the table, revert to the documented schema (top-level object with 'entries' list and 'ambiguous_bare_names' list) — the runtime lfx extension migration reader expects exactly that shape.
  3. If the error names the baseline side, check `git show <base>:src/lfx/src/lfx/extension/migration/migration_table.json | python -m json.tool` to see the malformed value at that ref, or point --baseline at a known-good copy.
  4. Validate locally before pushing: `python -c "import json;d=json.load(open('src/lfx/src/lfx/extension/migration/migration_table.json'));assert isinstance(d['entries'],list)"`.

Example fix

// before (migration_table.json)
{
  "entries": {
    "ChatInput": { "target": "ext:helpers:ChatInput" }
  },
  "ambiguous_bare_names": []
}

// after
{
  "entries": [
    { "bare_class_name": "ChatInput", "target": "ext:helpers:ChatInput@official" }
  ],
  "ambiguous_bare_names": []
}
Defensive patterns

Strategy: validation

Validate before calling

import json

MANIFEST = "src/lfx/src/lfx/extension/migration/migration_table.json"

def manifest_is_valid(path: str = MANIFEST) -> bool:
    data = json.loads(open(path, encoding="utf-8").read())
    return (
        isinstance(data, dict)
        and isinstance(data.get("entries", []), list)
        and isinstance(data.get("ambiguous_bare_names", []), list)
    )

assert manifest_is_valid(), "run before scripts/migrate/check_migration_append_only.py"

Type guard

def is_migration_manifest(data: object) -> bool:
    """Narrow parsed JSON to the migration-table shape."""
    if not isinstance(data, dict):
        return False
    entries = data.get("entries", [])
    ambig = data.get("ambiguous_bare_names", [])
    return (
        isinstance(entries, list)
        and all(isinstance(e, dict) for e in entries)
        and isinstance(ambig, list)
        and all(isinstance(e, dict) for e in ambig)
    )

Prevention

When it happens

Trigger: Running `python scripts/migrate/check_migration_append_only.py` (or with --baseline/--current) where src/lfx/src/lfx/extension/migration/migration_table.json, or the baseline fetched via `git show <base>:src/lfx/src/lfx/extension/migration/migration_table.json`, contains an 'entries' key whose value is not a JSON array — e.g. "entries": {...} or "entries": "none". A missing 'entries' key does NOT trigger it (data.get('entries', []) defaults to []).

Common situations: Hand-editing migration_table.json and accidentally nesting entries under a keyed object (e.g. grouping by provider) instead of an array; a merge conflict resolution that left a stray string; feeding a --baseline file that is actually a different schema (e.g. an export or report) rather than a migration table.

Related errors


AI-assisted analysis of langflow-ai/langflow@976ec789d2 (2026-08-14). Data as JSON: /api/errors/87055622ed17a923. Report an issue: GitHub.