CommandCodeAI / CommandCodeAI/command-code
todo_write removal-detection note causes an oscillating resubmission loop with literal-minded models (grok-4.6): case/rewording flips treated as "REMOVED unfinished items", recovery note mandates resubmission, never converges
还没有人认领这个 Issue。
- 主要语言
- 没有语言数据
- 星标
- 4k
- 派生
- 350
- PR 合并指标
- 30 天内没有已合并 PR
描述
Summary
The todo_write tool replaces the whole list and detects removals by exact content-string comparison (droppedUnfinished() in cli.mjs: old items whose status !== "completed" and whose content is not in the new set). When the model re-cases or rewords its own plan items between calls — e.g. Phase -1: Auto-update check vs Phase -1: Auto-Update Check, both copied from the skill's own headings — every cosmetic difference is classified as a removal of unfinished work.
Each such update appends a recovery note to the tool result:
NOTE: This update REMOVED N unfinished items from the previous list ("…"). Every todo_write call replaces the ENTIRE list. If you did not mean to drop them, call todo_write again with the complete list.
The note instructs resubmission but does not include the previous list verbatim and does not say which spelling is authoritative. A literal-minded model (observed on grok-4.6) interprets the note as "my list was mangled — restore the correct one", resubmits with the other casing, fires the note again, and oscillates indefinitely. In two real sessions the loop consumed 7 and 9 consecutive note firings (8 and 13 todo_write calls) before the user manually interrupted. The model's own reasoning shows the mechanism: "Restoring the full todo list first. Then running the update check…" — the restore always won, the real work never started.
The failure is model-robustness × harness-guidance, not provider-specific: the same model served through both BYOK (xkiro/x-ai/grok-4.6) and the Command Code catalog (xai/grok-4.6) receives and executes tool calls fine on the wire; the loop only emerges when in-session history primes the model to rephrase its plan items.
Expected Behavior
- Cosmetic differences in item
content(casing,&vsand, minor rewording) between consecutivetodo_writecalls should not be classified as removal of unfinished work, or at minimum should not trigger a recovery note that demands resubmission. - The recovery note should give the model a convergent action: either embed the previous list verbatim as the authoritative copy, or state that the current list is acceptable and the model should proceed without resubmitting.
- A model that follows the note's advice ("call todo_write again with the complete list") should exit the loop after one resubmission, not cycle indefinitely.
Actual Behavior
- Every re-cased/reworded resubmission fires the
REMOVED N unfinished itemsnote again (9 firings in one session across 13todo_writecalls; 7 firings across 8 calls in a second session). - The model oscillates between two spellings of the same items, burning turns on
todo_writewhile announcing the next real step in text each time ("I'll run Phase -1 and Phase 0…" — never executed). - The loop survives user steering attempts like "skip the auto-update check": the model applies the instruction but rewords an item in the process (
Auto-Update Check→Auto-Update Check (skipped)), re-triggering the note. - The user must manually interrupt ("stop touching todo_write, just proceed") to break the cycle; the model then proceeds normally, confirming tools and permissions were never the problem.
- One session ended with no user intervention available mid-run and burned turns to
max_turns.
Observed oscillation pairs (exact strings from session transcripts):
Phase -1: Auto-update check↔Phase -1: Auto-Update CheckPhase 0: Schema and template resolution↔Phase 0: Schema & Template ResolutionPhase 1: Five-lens discovery↔Phase 1: Interactive Discovery (5-lens Q&A)
Steps to reproduce the issue
Reproduction requires in-session state — the previous-list store (toolSessionState) is in-memory per process, so two chained cmd -p calls do not reproduce it (old list is empty in the second process). Drive it in a single session:
- Use a model prone to cosmetic rephrasing of plan items (grok-4.6 observed; likely any model with the same habit). Interactive TUI or headless both work as long as all calls happen in one session.
- Get the model to create a todo list whose items it will later re-spell. Reliably triggers when a loaded skill's own headings differ in casing from the model's natural phrasing (e.g.
openspec-plus-proposalmandates Phase -1/0/1/2 with Title Case headings):Use todo_write to create this checklist: [Phase -1: Auto-update check (in_progress), Phase 0: Resolve template (pending), Phase 1: Draft proposal (pending)]. - Instruct a list replacement that drops unfinished items — either explicitly ("replace the list with only item 1, still in_progress") or implicitly ("skip step 1 and mark it complete, then reword the remaining steps to match the skill's exact headings").
- Observe the tool result:
NOTE: This update REMOVED N unfinished items… call todo_write again with the complete list. - The model resubmits with the other casing → note fires again → repeat. Without user interruption the run exhausts its turn budget; with
--output-format jsonyou can count consecutiveREMOVEDfirings directly in the NDJSON stream.
Minimal deterministic-ish driver (single session, both steps in one run):
cmd -p "Do these steps in order: (1) todo_write: [A (in_progress), B (pending), C (pending), D (pending)]. (2) todo_write: replace with [a (in_progress)] — same item, different casing. (3) Continue with the actual task." \
--model <grok-4.6-id> --tools-enable todo_write --max-turns 12
Whether step 2 loops depends on the model noticing the note and "restoring"; forcing several drop cycles in one prompt ("now restore, now drop again") makes the note storm visible in any model that obeys the note literally.
Command Code Version
1.52.0
Operating System
Linux
Terminal/IDE
Unknown
Shell
command-code
Session file (optional)
No response
Fix prompt (optional)
Suggested remediations (any one breaks the oscillation; first is the real fix, others are defense in depth):
- Fuzzy-match removal detection. In
droppedUnfinished(), compare with normalization: casefold + collapse whitespace + strip punctuation (&/and,-/–) before set membership. A resubmission differing only in casing must never count as a removal. - Make the note convergent. When the note fires, append the previous list verbatim:
Previous list was: <json>. If the model is told to restore, give it the exact bytes to restore. Alternatively, add:If the removal was intentional, do NOT call todo_write again — proceed with the task. - Escalation cap. Track consecutive
REMOVEDfirings per session; after 2, replace the resubmission advice with a hard "stop calling todo_write; proceed with the task" instruction. - Deterministic item ids. Items already support an optional
idfield. Prefer id-match over content-match when ids are present, and encourage ids in the tool description so reworded items stay linked.
Additional context
- Environment: Command Code CLI 1.52.0 on Linux; model
xkiro/x-ai/grok-4.6(BYOK, OpenAI-completions wire) andxai/grok-4.6(Command Code catalog) — both exhibit the loop; the loop is absent on short fresh-context runs of the same model, so it is conversational-state dependent, not transport dependent. - Wire-level ruling-out: headless A/B tests on both servings show declared tools (
read_file) and progressively-disclosed tools (web_search, MCP tools) emit and execute correctly; prompt caching differences and odd tool-call IDs on BYOK are unrelated cosmetics. - Session evidence:
~/.commandcode/projects/home-pbtrudel-git-automation-hub/— session13a7a016…(9 REMOVED notes / 13 todo_write calls, user-interrupt recovered it), sessiona14438fa…(7 notes / 8 calls, ended at max-turns-adjacent stall). Transcripts show the model's thinking explicitly planning "restore the full list" every turn. - Interactive-mode note:
todo_writecalls render only as TODOS-panel updates (no tool block in the transcript view), so the loop looks to the user like the model endlessly announcing intent with nothing happening — expensive to diagnose from the TUI alone. - Related but distinct: the loop superficially resembles the known undeclared-tool/strict-wire issue (
toolcalls-issue.mdin the working notes), but that issue is about tools never being emitted; here tool calls fire fine and the failure is purely repeated no-optodo_writeresubmissions.
贡献指南
这个仓库没有索引到贡献指南
从这里开始
- 先读完整个 Issue,再读项目的贡献指南。
- 在 Issue 下留言说明你要接手 —— 这能避免两个人做同样的事。
- Fork 仓库,在一个分支上完成修改。
- 提交 Pull Request,并在描述里引用这个 Issue 编号。
调研方向
从 cli.mjs 中的 droppedUnfinished() 和 todo_write 恢复备注路径开始。运行提供的单会话 command driver,并检查 NDJSON 输出中的连续 REMOVED 备注。比较提议的匹配方式、备注措辞和升级处理方案;完成意味着仅为外观而进行的改写不会创建不收敛的重新提交循环,并且有意删除的内容仍然易于理解。
由索引模型根据 Issue 内容生成。
评估
- 技术栈
- javascript
- 领域
- cli, tooling
- Issue 类型
- 缺陷
- 难度
- 4/5
- 预计耗时
- 3-5 天
- 活跃度
- 活跃
- 描述清晰度
- 基本清楚
- 新手友好度
- 52/100