HKUDS/Vibe-Trading · error · ValueError

audit must be a list

Error message

audit must be a list

What it means

_coerce_audit_rows accepts None, '' (empty), a JSON string, or a list; anything else that is not a list after JSON decoding raises this. It guards the audit parameter of the goal tool against malformed LLM/tool arguments before row-level validation begins.

Source

Thrown at agent/src/tools/goal_tool.py:42

def _coerce_string_list(value: Any) -> list[str]:
    """Coerce a JSON-schema array-or-string value to a string list."""
    if value is None:
        return []
    if isinstance(value, str):
        return [value] if value.strip() else []
    if isinstance(value, list):
        return [str(item).strip() for item in value if str(item).strip()]
    return []


def _coerce_audit_rows(value: Any) -> list[AuditRow]:
    """Coerce model/API-style audit rows into dataclasses."""
    if value in (None, ""):
        return []
    if isinstance(value, str):
        value = json.loads(value)
    if not isinstance(value, list):
        raise ValueError("audit must be a list")

    rows: list[AuditRow] = []
    for item in value:
        if not isinstance(item, dict):
            raise ValueError("audit rows must be objects")
        criterion_id = str(item.get("criterion_id") or "").strip()
        result = str(item.get("result") or "").strip()
        if not criterion_id or not result:
            raise ValueError("audit rows require criterion_id and result")
        rows.append(
            AuditRow(
                criterion_id=criterion_id,
                result=result,
                evidence_ids=_coerce_string_list(item.get("evidence_ids")),
                notes=str(item.get("notes") or ""),
            )
        )
    return rows

View on GitHub (pinned to 80ffdda44c)

Solutions

  1. Wrap single row objects in a list: audit=[{...}] or '[{...}]'
  2. If rows arrive as a dict keyed by criterion, convert with list(rows_dict.values()) before passing
  3. Catch ValueError from execute and re-prompt the model with the expected schema

Example fix

# before
execute(audit={"criterion_id": "c1", "result": "pass"})
# after
execute(audit=[{"criterion_id": "c1", "result": "pass"}])
Defensive patterns

Strategy: validation

Validate before calling

import json
def normalize_audit(v):
    if v in (None, ""):
        return []
    if isinstance(v, str):
        v = json.loads(v)
    if isinstance(v, dict):
        v = [v]
    assert isinstance(v, list), "audit must be a list"
    return v

Type guard

def is_audit_list(v) -> bool:
    import json
    if isinstance(v, str):
        try:
            v = json.loads(v)
        except json.JSONDecodeError:
            return False
    return isinstance(v, list)

Try / catch

try:
    out = execute(audit=raw)
except ValueError as e:
    if str(e) == "audit must be a list":
        raw = [raw] if isinstance(raw, dict) else list(raw.values())
        out = execute(audit=raw)
    raise

Prevention

When it happens

Trigger: Passing audit as a dict ({"criterion_id": ...}), a number, or a JSON string that decodes to an object rather than an array, e.g. '{"0": {...}}'.

Common situations: LLM emitting a single audit row object instead of an array; JSON string containing an object map keyed by index; hand-written payloads wrapping rows in extra braces.

Understand the failure class

Background: Schema validation failed / invalid input schema: payload rejected because its shape doesn't match the expected schema — this error's family across 28 libraries.

Related errors


AI-assisted analysis of HKUDS/Vibe-Trading@80ffdda44c (2026-08-28). Data as JSON: /api/errors/4f71a53c7353bf07. Report an issue: GitHub.