{"record":{"id":"4f71a53c7353bf07","repo":"HKUDS/Vibe-Trading","slug":"audit-must-be-a-list","errorCode":null,"errorMessage":"audit must be a list","messagePattern":"audit must be a list","errorType":"validation","errorClass":"ValueError","httpStatus":null,"severity":"error","filePath":"agent/src/tools/goal_tool.py","lineNumber":42,"sourceCode":"def _coerce_string_list(value: Any) -> list[str]:\n    \"\"\"Coerce a JSON-schema array-or-string value to a string list.\"\"\"\n    if value is None:\n        return []\n    if isinstance(value, str):\n        return [value] if value.strip() else []\n    if isinstance(value, list):\n        return [str(item).strip() for item in value if str(item).strip()]\n    return []\n\n\ndef _coerce_audit_rows(value: Any) -> list[AuditRow]:\n    \"\"\"Coerce model/API-style audit rows into dataclasses.\"\"\"\n    if value in (None, \"\"):\n        return []\n    if isinstance(value, str):\n        value = json.loads(value)\n    if not isinstance(value, list):\n        raise ValueError(\"audit must be a list\")\n\n    rows: list[AuditRow] = []\n    for item in value:\n        if not isinstance(item, dict):\n            raise ValueError(\"audit rows must be objects\")\n        criterion_id = str(item.get(\"criterion_id\") or \"\").strip()\n        result = str(item.get(\"result\") or \"\").strip()\n        if not criterion_id or not result:\n            raise ValueError(\"audit rows require criterion_id and result\")\n        rows.append(\n            AuditRow(\n                criterion_id=criterion_id,\n                result=result,\n                evidence_ids=_coerce_string_list(item.get(\"evidence_ids\")),\n                notes=str(item.get(\"notes\") or \"\"),\n            )\n        )\n    return rows","sourceCodeStart":24,"sourceCodeEnd":60,"githubUrl":"https://github.com/HKUDS/Vibe-Trading/blob/80ffdda44c5c4db0dd84d70e051cca591cea67df/agent/src/tools/goal_tool.py#L24-L60","documentation":"_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.","triggerScenarios":"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\": {...}}'.","commonSituations":"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.","solutions":["Wrap single row objects in a list: audit=[{...}] or '[{...}]'","If rows arrive as a dict keyed by criterion, convert with list(rows_dict.values()) before passing","Catch ValueError from execute and re-prompt the model with the expected schema"],"exampleFix":"# before\nexecute(audit={\"criterion_id\": \"c1\", \"result\": \"pass\"})\n# after\nexecute(audit=[{\"criterion_id\": \"c1\", \"result\": \"pass\"}])","handlingStrategy":"validation","validationCode":"import json\ndef normalize_audit(v):\n    if v in (None, \"\"):\n        return []\n    if isinstance(v, str):\n        v = json.loads(v)\n    if isinstance(v, dict):\n        v = [v]\n    assert isinstance(v, list), \"audit must be a list\"\n    return v","typeGuard":"def is_audit_list(v) -> bool:\n    import json\n    if isinstance(v, str):\n        try:\n            v = json.loads(v)\n        except json.JSONDecodeError:\n            return False\n    return isinstance(v, list)","tryCatchPattern":"try:\n    out = execute(audit=raw)\nexcept ValueError as e:\n    if str(e) == \"audit must be a list\":\n        raw = [raw] if isinstance(raw, dict) else list(raw.values())\n        out = execute(audit=raw)\n    raise","preventionTips":["Always wrap rows in [] even for a single row","JSON-encode as an array, not an object","Use pydantic models for tool args to enforce shape at the boundary"],"tags":["python","json","validation","llm-io"],"backgroundTag":"schema-validation-failed","analyzedSha":"80ffdda44c5c4db0dd84d70e051cca591cea67df","analyzedAt":"2026-08-28T12:46:38.989Z","schemaVersion":2},"datasetVersion":"2026-08-28T16:17:29.566Z"}