{"record":{"id":"adcba3b63c41d5b9","repo":"usestrix/strix","slug":"updates-must-be-valid-json","errorCode":null,"errorMessage":"Updates must be valid JSON","messagePattern":"Updates must be valid JSON","errorType":"validation","errorClass":"ValueError","httpStatus":null,"severity":"warning","filePath":"strix/tools/todo/tools.py","lineNumber":164,"sourceCode":"            return [str(item).strip() for item in data if str(item).strip()]\n        return [str(data).strip()]\n    if isinstance(raw_ids, list):\n        return [str(item).strip() for item in raw_ids if str(item).strip()]\n    return [str(raw_ids).strip()]\n\n\ndef _normalize_bulk_updates(raw_updates: Any) -> list[dict[str, Any]]:\n    if raw_updates is None:\n        return []\n    data: Any = raw_updates\n    if isinstance(raw_updates, str):\n        stripped = raw_updates.strip()\n        if not stripped:\n            return []\n        try:\n            data = json.loads(stripped)\n        except json.JSONDecodeError as e:\n            raise ValueError(\"Updates must be valid JSON\") from e\n\n    if isinstance(data, dict):\n        data = [data]\n    if not isinstance(data, list):\n        raise TypeError(\"Updates must be a list of update objects\")\n\n    normalized: list[dict[str, Any]] = []\n    for item in data:\n        if not isinstance(item, dict):\n            raise TypeError(\"Each update must be an object with todo_id\")\n        todo_id = item.get(\"todo_id\") or item.get(\"id\")\n        if not todo_id:\n            raise ValueError(\"Each update must include 'todo_id'\")\n        normalized.append(\n            {\n                \"todo_id\": str(todo_id).strip(),\n                \"title\": item.get(\"title\"),\n                \"description\": item.get(\"description\"),","sourceCodeStart":146,"sourceCodeEnd":182,"githubUrl":"https://github.com/usestrix/strix/blob/85513391305171ecc6faffe03da4a8bda5e3febb/strix/tools/todo/tools.py#L146-L182","documentation":"_normalize_bulk_updates accepts the updates argument either as a JSON string or as already-decoded data. If a non-empty string is passed, it json.loads it; a JSONDecodeError is re-raised as ValueError('Updates must be valid JSON'). Empty/whitespace strings return [] (no updates) rather than erroring.","triggerScenarios":"Calling a bulk todo update tool with a string that isn't valid JSON — trailing commas, single quotes, Python-repr dicts ({'id': 't1'}), or truncated JSON from an LLM. Only raises for non-empty strings that fail to parse.","commonSituations":"LLM emitting Python-literal style dicts instead of JSON; hand-typed JSON with comments; copy-paste losing a closing brace; passing an already-decoded object wrapped in str().","solutions":["Pass strict JSON: double quotes, no trailing commas, no comments — or better, pass a decoded list/dict directly instead of a string.","Validate first: json.loads(s) in a try/except before calling the tool.","For LLM callers, instruct JSON-only output and consider json mode / structured outputs."],"exampleFix":"# before (Python-literal quotes — invalid JSON)\nbulk_update_todos(updates=\"[{'id': 't1', 'status': 'done'}]\")\n\n# after\nbulk_update_todos(updates='[{\"id\": \"t1\", \"status\": \"done\"}]')\n# or pass decoded data directly:\nbulk_update_todos(updates=[{\"id\": \"t1\", \"status\": \"done\"}])","handlingStrategy":"validation","validationCode":"import json\n\ndef parse_updates_arg(updates):\n    if isinstance(updates, str):\n        s = updates.strip()\n        if not s:\n            return []\n        return json.loads(s)  # raises here with a precise JSON error\n    return updates\n\n# pre-check before calling the tool\njson.loads(updates_str)  # will raise json.JSONDecodeError, not the tool's ValueError","typeGuard":null,"tryCatchPattern":"try:\n    bulk_update_todos(updates=raw)\nexcept ValueError as exc:\n    if \"valid JSON\" in str(exc):\n        raw = json.dumps(fix_python_literals(raw))  # repair quotes/commas, retry once\n        bulk_update_todos(updates=raw)\n    else:\n        raise","preventionTips":["Pass decoded Python objects instead of JSON strings when your caller can.","Use strict JSON: double quotes, no trailing commas, no comments.","For LLM callers, enable structured/JSON output modes and validate before the call."],"tags":["todo","json","validation","llm-output"],"backgroundTag":null,"analyzedSha":"85513391305171ecc6faffe03da4a8bda5e3febb","analyzedAt":"2026-08-15T05:03:57.275Z","schemaVersion":2},"datasetVersion":"2026-08-15T17:31:12.345Z"}