Skip to content

fix(todo): guard create_todo against 6-char id collisions#696

Open
SuperMarioYL wants to merge 1 commit into
usestrix:mainfrom
SuperMarioYL:fix/todo-id-collision-guard
Open

fix(todo): guard create_todo against 6-char id collisions#696
SuperMarioYL wants to merge 1 commit into
usestrix:mainfrom
SuperMarioYL:fix/todo-id-collision-guard

Conversation

@SuperMarioYL

Copy link
Copy Markdown

What

create_todo generates a per-agent todo id with no uniqueness check:

todo_id = str(uuid.uuid4())[:6]
...
agent_todos[todo_id] = { ...new todo... }
created.append({"todo_id": todo_id, ...})

Ids are only 6 hex chars (str(uuid.uuid4())[:6], ~16.7M space), so two
todos for the same agent can collide on their first six chars. When that
happens the second assignment silently overwrites the existing todo in
agent_todos, yet the tool still returns success: True and reports it as
created — the prior todo's data is lost with no error.

Why it matters (and the parity gap)

The sibling notes tool uses the same 6-char scheme and already guards
against exactly this:

# strix/tools/notes/tools.py
def _generate_note_id() -> str | None:
    for _ in range(_NOTE_ID_GENERATION_ATTEMPTS):
        note_id = uuid.uuid4().hex[:6]
        if note_id not in _notes_storage:
            return note_id
    return None

Git history shows the guard was a deliberate fix — 940319f ("Align note IDs
with todo IDs (6-char hex)") aligned note ids to the todo scheme, then #630
("avoid note ID collisions") added the retry guard to notes. todo — the
original of that 6-char scheme — was never given the same protection.

This is orthogonal to #663 / #670, which fix the id lookup path
(_normalize_todo_ids mangling numeric-looking ids). This PR fixes the id
generation path (create_todo), a different function with no overlap.

Reproduction (current main)

Forcing a collision (existing id abcdef, next uuid4() also maps to abcdef):

success: True
abcdef title now: NEW      # the existing "KEEP" todo was silently overwritten
total todos: 1             # should be 2 — one todo was lost

The fix

  • Add _generate_todo_id(agent_todos), which retries to an unused slug and
    returns None if the space is exhausted (mirrors notes._generate_note_id).
  • create_todo now surfaces an exhausted id space as a per-task error,
    mirroring the module's existing bulk-error handling in update/mark/
    delete, instead of overwriting. The normal-path response shape is
    unchanged (errors only appears when a task actually fails).

Tests

New tests/test_todo_id_generation.py (separate file to avoid colliding with
#663's tests/test_todo.py) covers: collision retry, id-space exhaustion, the
end-to-end no-overwrite guarantee, and the exhaustion-reports-error path. Full
suite: 88 passed. ruff, mypy strix/, and bandit are clean.

Todo ids are `str(uuid.uuid4())[:6]` slugs (~16.7M space), so two todos
for the same agent can collide on their first six hex chars. create_todo
assigned the id with no uniqueness check, so a collision silently
overwrote the existing todo while still reporting success — losing the
prior todo's data.

notes uses the same 6-char scheme and already guards this via
_generate_note_id (added in usestrix#630, after note ids were aligned to the todo
scheme in 940319f); todo, the original of that scheme, never got the
guard.

Add _generate_todo_id, which retries to an unused slug and returns None
when the space is exhausted, and surface exhaustion as a per-task error
(mirroring the module's existing bulk-error handling in update/mark/
delete) instead of overwriting. Add regression tests covering collision
retry, exhaustion, and the end-to-end no-overwrite guarantee.
@greptile-apps

greptile-apps Bot commented Jul 5, 2026

Copy link
Copy Markdown
Contributor

Greptile Summary

This PR prevents todo ID collisions from overwriting existing todos. The main changes are:

  • Added a bounded helper for per-agent todo ID generation.
  • Updated create_todo to skip tasks when no unique ID can be generated.
  • Added tests for collision retry, exhausted IDs, and no-overwrite behavior.

Confidence Score: 5/5

The changed flow looks mergeable after a small cleanup to partial-create diagnostics.

  • The overwrite bug is fixed for normal collision retry.
  • Successful partial creates are persisted.
  • A mixed success-and-error batch can be shown as a generic failure, hiding the created todo IDs from the UI.

strix/tools/todo/tools.py

Important Files Changed

Filename Overview
strix/tools/todo/tools.py Adds unique todo ID generation and per-task creation errors, with one partial-success response shape that can hide created todo IDs from the renderer.
tests/test_todo_id_generation.py Adds focused tests for retrying collisions, handling exhausted ID generation, and preserving existing todos.
Prompt To Fix All With AI
Fix the following 1 code review issue. Work through them one at a time, proposing concise fixes.

---

### Issue 1 of 1
strix/tools/todo/tools.py:353
**Partial Create Looks Failed**

When a batched create has both successful tasks and an ID-generation exhaustion, this returns `success: false` even though `created` contains persisted todos. The create-todo renderer branches on `success` and falls back to a generic `Failed to create todo` message because this response has `errors` but no top-level `error`, so users lose the IDs for the todos that were actually created.

Reviews (1): Last reviewed commit: "fix(todo): guard create_todo against 6-c..." | Re-trigger Greptile

Comment thread strix/tools/todo/tools.py
if created:
_persist()
response: dict[str, Any] = {
"success": len(errors) == 0,

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Partial Create Looks Failed

When a batched create has both successful tasks and an ID-generation exhaustion, this returns success: false even though created contains persisted todos. The create-todo renderer branches on success and falls back to a generic Failed to create todo message because this response has errors but no top-level error, so users lose the IDs for the todos that were actually created.

Prompt To Fix With AI
This is a comment left during a code review.
Path: strix/tools/todo/tools.py
Line: 353

Comment:
**Partial Create Looks Failed**

When a batched create has both successful tasks and an ID-generation exhaustion, this returns `success: false` even though `created` contains persisted todos. The create-todo renderer branches on `success` and falls back to a generic `Failed to create todo` message because this response has `errors` but no top-level `error`, so users lose the IDs for the todos that were actually created.

How can I resolve this? If you propose a fix, please make it concise.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant