Skip to content

todowrite runaway: 9,139 calls/session escapes per-message doom-loop detector #834

Description

@anandgupta42

Problem

Telemetry-2026-05-21 (2,503 machines, 14 days) shows the todowrite loop pattern from March is still active and worse:

  • 29,562 total todowrite calls in the window
  • Top 3 machines = 63% of all calls
  • Single machine peak: 9,139 todowrite calls across 28 sessions (~325/session)

Sessions at this scale burn tokens, induce context bloat, and produce visibly broken UX. The existing doom-loop detector in session/processor.ts (TOOL_REPEAT_THRESHOLD = 30) is per-assistant-message scope: its counter resets every time a new assistant message starts. The pathological pattern is slow-burn — ~10-15 calls per LLM turn, accumulating across many turns within a single session.

Root cause

SessionProcessor.create() instantiates a fresh toolCallCounts: Record<string, number> per message. A new processor is created per assistant message, so the counter never accumulates across the full session. The detector only catches a single turn that spams a tool 30+ times.

Real loops look like this: agent calls todowrite 10x in turn 1, 10x in turn 2, 10x in turn 3... totaling 100 within 10 turns. Each turn is under threshold; the cross-turn pattern is invisible to the per-message counter.

Fix

Add a tool-level per-session counter inside TodoWriteTool itself. New constants:

  • TODOWRITE_WARN_THRESHOLD = 25 — emit warning in tool output + telemetry event; the agent sees the warning text and can self-correct
  • TODOWRITE_REFUSE_THRESHOLD = 50 — refuse the call entirely; return a structured message telling the agent to stop calling todowrite and continue with the work

Both thresholds emit doom_loop_detected telemetry events so dashboards see the pattern even when the agent ignores the warning.

Extracted into a testable helper recordTodoWriteCall(sessionID) returning { action: "ok" | "warn" | "refuse", count }. Counter is module-scoped in-memory Map; stale entries from old sessions are harmless because lookup is per-sessionID.

Impact

Telemetry should show:

  • todowrite call volume per session capped at ~50 (down from 9,000+ peaks)
  • doom_loop_detected events for todowrite at much higher rates (every session that crosses the threshold now reports)
  • Reduced token/context burn from runaway todo updates

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't working

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions