usestrix/strix · warning · ValueError

Updates must be valid JSON

Error message

Updates must be valid JSON

What it means

_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.

Source

Thrown at strix/tools/todo/tools.py:164

            return [str(item).strip() for item in data if str(item).strip()]
        return [str(data).strip()]
    if isinstance(raw_ids, list):
        return [str(item).strip() for item in raw_ids if str(item).strip()]
    return [str(raw_ids).strip()]


def _normalize_bulk_updates(raw_updates: Any) -> list[dict[str, Any]]:
    if raw_updates is None:
        return []
    data: Any = raw_updates
    if isinstance(raw_updates, str):
        stripped = raw_updates.strip()
        if not stripped:
            return []
        try:
            data = json.loads(stripped)
        except json.JSONDecodeError as e:
            raise ValueError("Updates must be valid JSON") from e

    if isinstance(data, dict):
        data = [data]
    if not isinstance(data, list):
        raise TypeError("Updates must be a list of update objects")

    normalized: list[dict[str, Any]] = []
    for item in data:
        if not isinstance(item, dict):
            raise TypeError("Each update must be an object with todo_id")
        todo_id = item.get("todo_id") or item.get("id")
        if not todo_id:
            raise ValueError("Each update must include 'todo_id'")
        normalized.append(
            {
                "todo_id": str(todo_id).strip(),
                "title": item.get("title"),
                "description": item.get("description"),

View on GitHub (pinned to 8551339130)

Solutions

  1. Pass strict JSON: double quotes, no trailing commas, no comments — or better, pass a decoded list/dict directly instead of a string.
  2. Validate first: json.loads(s) in a try/except before calling the tool.
  3. For LLM callers, instruct JSON-only output and consider json mode / structured outputs.

Example fix

# before (Python-literal quotes — invalid JSON)
bulk_update_todos(updates="[{'id': 't1', 'status': 'done'}]")

# after
bulk_update_todos(updates='[{"id": "t1", "status": "done"}]')
# or pass decoded data directly:
bulk_update_todos(updates=[{"id": "t1", "status": "done"}])
Defensive patterns

Strategy: validation

Validate before calling

import json

def parse_updates_arg(updates):
    if isinstance(updates, str):
        s = updates.strip()
        if not s:
            return []
        return json.loads(s)  # raises here with a precise JSON error
    return updates

# pre-check before calling the tool
json.loads(updates_str)  # will raise json.JSONDecodeError, not the tool's ValueError

Try / catch

try:
    bulk_update_todos(updates=raw)
except ValueError as exc:
    if "valid JSON" in str(exc):
        raw = json.dumps(fix_python_literals(raw))  # repair quotes/commas, retry once
        bulk_update_todos(updates=raw)
    else:
        raise

Prevention

When it happens

Trigger: 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.

Common situations: 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().

Related errors


AI-assisted analysis of usestrix/strix@8551339130 (2026-08-15). Data as JSON: /api/errors/adcba3b63c41d5b9. Report an issue: GitHub.