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
まだ誰も着手していません。
- 主要言語
- 言語のデータがありません
- スター
- 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 にコメントします — 二人が同じ作業をするのを防げます。
- リポジトリをフォークし、ブランチを切って変更します。
- issue 番号を参照したプルリクエストを送ります。
調査の方向性
cli.mjs の droppedUnfinished() と todo_write のリカバリーノートのパスから始めます。提供されている単一セッション用の command driver を実行し、NDJSON 出力内の連続する REMOVED ノートを調べます。提案されたマッチング、ノートの文言、エスカレーションのアプローチを比較します。完了とは、表面的な言い換えによって収束しない再送ループが発生せず、意図的な削除が理解可能なままであることを意味します。
索引モデルが issue の本文から書いたものです。
評価
- 技術スタック
- javascript
- 領域
- cli, tooling
- issue の種類
- バグ
- 難易度
- 4/5
- 見積もり時間
- 3〜5日
- 活発さ
- 活発
- 明瞭さ
- おおむね明確
- 初心者へのやさしさ
- 52/100