From b191e6c5ff73ee3ba73ea14cbb0d033080e19df8 Mon Sep 17 00:00:00 2001 From: can1357 Date: Tue, 19 May 2026 19:18:44 +0900 Subject: [PATCH] fix(coding-agent): reject task-N ID confusion, error on partial todo apply - todo-write.md adds an explicit note that tasks are referenced by verbatim content text; the tool never emits task-N IDs. - resolveTaskOrError rejects ^task-\d+$ inputs with a clarifying error. - execute sets isError:true when any op failed. - appendItems short-circuits on the first "already exists" error so the call no longer applies the prefix of a doomed batch. --- .../coding-agent/src/prompts/tools/todo-write.md | 2 ++ packages/coding-agent/src/tools/todo-write.ts | 15 +++++++++++---- 2 files changed, 13 insertions(+), 4 deletions(-) diff --git a/packages/coding-agent/src/prompts/tools/todo-write.md b/packages/coding-agent/src/prompts/tools/todo-write.md index cfe67f542..34221d17c 100644 --- a/packages/coding-agent/src/prompts/tools/todo-write.md +++ b/packages/coding-agent/src/prompts/tools/todo-write.md @@ -1,3 +1,5 @@ +**Tasks are referenced by their verbatim content string, not by any auto-generated ID. There is no "task-1"/"task-N" identifier — the tool never emits one. Pass the task's content text in the `task` field.** + Manages a phased task list. Pass `ops`: a flat array of operations. The next pending task is auto-promoted to `in_progress` after each completion. Allowed `op` values are only `init`, `start`, `done`, `drop`, `rm`, `append`, and `note`. `pending` is a task status, not an `op`; leave not-yet-started tasks implicit in `init`/`append` lists. diff --git a/packages/coding-agent/src/tools/todo-write.ts b/packages/coding-agent/src/tools/todo-write.ts index ba9f8e56c..5541af834 100644 --- a/packages/coding-agent/src/tools/todo-write.ts +++ b/packages/coding-agent/src/tools/todo-write.ts @@ -150,9 +150,15 @@ function resolveTaskOrError( } const hit = findTaskByContent(phases, content); if (!hit) { - const totalTasks = phases.reduce((sum, phase) => sum + phase.tasks.length, 0); - const hint = totalTasks === 0 ? " (todo list is empty — was it replaced or not yet created?)" : ""; - errors.push(`Task "${content}" not found${hint}`); + if (/^task-\d+$/.test(content)) { + errors.push( + `Task "${content}" not found. Tasks are referenced by content, not by IDs — pass the task's full text from the previous result.`, + ); + } else { + const totalTasks = phases.reduce((sum, phase) => sum + phase.tasks.length, 0); + const hint = totalTasks === 0 ? " (todo list is empty — was it replaced or not yet created?)" : ""; + errors.push(`Task "${content}" not found${hint}`); + } } return hit; } @@ -209,7 +215,7 @@ function appendItems(phases: TodoPhase[], entry: TodoOpEntryValue, errors: strin for (const content of entry.items) { if (findTaskByContent(phases, content)) { errors.push(`Task "${content}" already exists`); - continue; + return phases; } phase.tasks.push({ content, status: "pending" }); } @@ -513,6 +519,7 @@ export class TodoWriteTool implements AgentTool 0 ? true : undefined, }; } }