langflow-ai/langflow · error · SystemExit

error: {source} top-level value must be an object

Error message

error: {source} top-level value must be an object

What it means

Printed and raised (exit code 2) by _parse in check_migration_append_only.py when the migration-table JSON parses successfully but its top-level value is not an object — e.g. the file is a JSON array or a bare string/number. The schema requires an object with `entries` (and optional `ambiguous_bare_names`) lists, so the script refuses to compare.

Source

Thrown at scripts/migrate/check_migration_append_only.py:96

        # "no baseline" and return None.
        return None
    return completed.stdout


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)

View on GitHub (pinned to 976ec789d2)

Solutions

  1. Wrap the content in an object with the required keys: {"entries": [...], "ambiguous_bare_names": [...]}
  2. Confirm `entries` and (if present) `ambiguous_bare_names` are lists, or the next validation will also fail
  3. If the format change is intentional, update _parse and downstream comparison logic in the same commit

Example fix

# before
[
  {"name": "0001_initial", "digest": "abc"}
]

# after
{
  "entries": [
    {"name": "0001_initial", "digest": "abc"}
  ],
  "ambiguous_bare_names": []
}
Defensive patterns

Strategy: type-guard

Validate before calling

import json
data = json.loads(raw)
if not isinstance(data, dict) or not isinstance(data.get("entries", []), list):
    raise ValueError("migration table must be an object with an `entries` list")

Type guard

def is_migration_table(data: object) -> bool:
    return (
        isinstance(data, dict)
        and isinstance(data.get("entries", []), list)
        and isinstance(data.get("ambiguous_bare_names", []), list)
    )

Try / catch

# the script exits 2; catch at the CI step level
data = json.loads(raw)
if not isinstance(data, dict):
    print(f"expected object, got {type(data).__name__}"); sys.exit(2)

Prevention

When it happens

Trigger: Rewriting the migration table as a bare array of entries ([{...},{...}]) or wrapping it in a string; json.loads succeeds, the isinstance(data, dict) check fails, and the script exits 2.

Common situations: Refactoring the table format without updating the script's expectation; generating the file with a tool that emits top-level arrays; partially applied format migrations.

Related errors


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