Component
strix/tools/todo/tools.py — _normalize_todo_ids
Environment
- Strix version:
main (302efed)
- Python / OS: deterministic pure-logic bug; reproducible on any Python 3.12+ and any OS
- LLM: N/A (no model involved to reproduce)
- Traceback: N/A — no exception; the tool returns
"Todo with ID 'inf' not found" and silently no-ops on the real todo
Description
_normalize_todo_ids accepts a JSON array, a comma-separated string, or a single bare id. It runs the input through json.loads and, when the parsed value isn't a list, returns str(parsed):
try:
data = json.loads(stripped)
except json.JSONDecodeError:
data = stripped.split(",") if "," in stripped else [stripped]
if isinstance(data, list):
return [str(item).strip() for item in data if str(item).strip()]
return [str(data).strip()] # <-- str() of a parsed JSON scalar
Todo ids are generated as str(uuid.uuid4())[:6] — 6-char hex slugs. Slugs like 1e5230, 2363e0, 0e4440 are valid JSON numbers:
json.loads("1e5230") → float('inf') → "inf"
json.loads("2363e0") → 2363.0 → "2363.0"
So mark_todo_done, mark_todo_pending, and delete_todo operate on the wrong (non-existent) id whenever such an id is passed as a bare string — a form the comma/split fallback explicitly supports.
Steps to reproduce
from strix.tools.todo.tools import _normalize_todo_ids
_normalize_todo_ids("1e5230") # a real 6-char uuid slug
Or end-to-end: create a todo whose id is 1e5230, then call mark_todo_done(todo_ids="1e5230") (bare string, not wrapped in an array).
Expected
["1e5230"]; the referenced todo is marked done.
Actual
["inf"]; the tool reports Todo with ID 'inf' not found and the real todo is left untouched.
Proposed fix
#663 — use json.loads only to detect the JSON array form; treat any non-array input as a literal id (optionally comma-separated).
Component
strix/tools/todo/tools.py—_normalize_todo_idsEnvironment
main(302efed)"Todo with ID 'inf' not found"and silently no-ops on the real todoDescription
_normalize_todo_idsaccepts a JSON array, a comma-separated string, or a single bare id. It runs the input throughjson.loadsand, when the parsed value isn't a list, returnsstr(parsed):Todo ids are generated as
str(uuid.uuid4())[:6]— 6-char hex slugs. Slugs like1e5230,2363e0,0e4440are valid JSON numbers:json.loads("1e5230")→float('inf')→"inf"json.loads("2363e0")→2363.0→"2363.0"So
mark_todo_done,mark_todo_pending, anddelete_todooperate on the wrong (non-existent) id whenever such an id is passed as a bare string — a form the comma/split fallback explicitly supports.Steps to reproduce
Or end-to-end: create a todo whose id is
1e5230, then callmark_todo_done(todo_ids="1e5230")(bare string, not wrapped in an array).Expected
["1e5230"]; the referenced todo is marked done.Actual
["inf"]; the tool reportsTodo with ID 'inf' not foundand the real todo is left untouched.Proposed fix
#663 — use
json.loadsonly to detect the JSON array form; treat any non-array input as a literal id (optionally comma-separated).