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

Đang mở
#826 1 bình luận 0 reaction 0 người được giao Xem trên GitHub

Chưa có ai nhận issue này.

Ngôn ngữ chính
Không có dữ liệu ngôn ngữ
Star
4k
Fork
350
Chỉ số merge pull request
Không có pull request nào được merge trong 30 ngày

Mô tả

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, & vs and, minor rewording) between consecutive todo_write calls 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 items note again (9 firings in one session across 13 todo_write calls; 7 firings across 8 calls in a second session).
  • The model oscillates between two spellings of the same items, burning turns on todo_write while 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 CheckAuto-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 checkPhase -1: Auto-Update Check
  • Phase 0: Schema and template resolutionPhase 0: Schema & Template Resolution
  • Phase 1: Five-lens discoveryPhase 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:

  1. 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.
  2. 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-proposal mandates 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)].
    
  3. 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").
  4. Observe the tool result: NOTE: This update REMOVED N unfinished items… call todo_write again with the complete list.
  5. The model resubmits with the other casing → note fires again → repeat. Without user interruption the run exhausts its turn budget; with --output-format json you can count consecutive REMOVED firings 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):

  1. 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.
  2. 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.
  3. Escalation cap. Track consecutive REMOVED firings per session; after 2, replace the resubmission advice with a hard "stop calling todo_write; proceed with the task" instruction.
  4. Deterministic item ids. Items already support an optional id field. 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) and xai/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/ — session 13a7a016… (9 REMOVED notes / 13 todo_write calls, user-interrupt recovered it), session a14438fa… (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_write calls 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.md in the working notes), but that issue is about tools never being emitted; here tool calls fire fine and the failure is purely repeated no-op todo_write resubmissions.

Hướng dẫn đóng góp

Chưa lập chỉ mục được hướng dẫn đóng góp cho kho mã nguồn này

Bắt đầu từ đâu

  1. Đọc hết issue, rồi đọc hướng dẫn đóng góp của dự án.
  2. Bình luận trên issue rằng bạn sẽ nhận — tránh hai người làm cùng một việc.
  3. Fork repository và làm thay đổi trên một nhánh.
  4. Mở pull request có tham chiếu số hiệu của issue.

Hướng nghiên cứu

Bắt đầu trong cli.mjs tại droppedUnfinished() và đường dẫn ghi chú khôi phục của todo_write. Chạy command driver cho một phiên duy nhất được cung cấp và kiểm tra các ghi chú REMOVED liên tiếp trong đầu ra NDJSON. So sánh cách đối sánh được đề xuất, cách diễn đạt ghi chú và các phương án chuyển cấp; hoàn tất nghĩa là việc viết lại mang tính hình thức không tạo ra vòng lặp gửi lại không hội tụ và các lần xóa có chủ ý vẫn dễ hiểu.

Do mô hình lập chỉ mục viết ra từ nội dung của issue.

Đánh giá

Công nghệ
javascript
Lĩnh vực
cli, tooling
Loại issue
Lỗi
Độ khó
4/5
Thời gian dự kiến
3-5 ngày
Mức độ hoạt động
Sôi nổi
Độ rõ ràng
Khá rõ ràng
Mức phù hợp với người mới
52/100

Nhận issue mới trong hộp thư của bạn

Bản tóm tắt ngắn những issue GitHub phù hợp với người mới.