From 2b6b08345aa1188937decba63cb86cb5cf472e40 Mon Sep 17 00:00:00 2001 From: MoerAI Date: Mon, 16 Mar 2026 10:24:57 +0900 Subject: [PATCH] fix(todo-continuation-enforcer): add plan agent to DEFAULT_SKIP_AGENTS to prevent infinite loop The todo-continuation-enforcer injects continuation prompts when sessions go idle with pending todos. When Plan Mode agents (which are read-only) create todo items, the continuation prompt contradicts Plan Mode's STRICTLY FORBIDDEN directive, causing an infinite loop where the agent acknowledges the conflict then goes idle, triggering another injection. Adding 'plan' to DEFAULT_SKIP_AGENTS prevents continuation injection into Plan Mode sessions, matching the same exclusion pattern already used for prometheus and compaction agents. Fixes #2526 --- .../todo-continuation-enforcer/AGENTS.md | 2 +- .../todo-continuation-enforcer/constants.ts | 2 +- .../continuation-injection.test.ts | 34 +++++++++++++++++++ 3 files changed, 36 insertions(+), 2 deletions(-) diff --git a/src/hooks/todo-continuation-enforcer/AGENTS.md b/src/hooks/todo-continuation-enforcer/AGENTS.md index 5df375c79..afcb5f3ac 100644 --- a/src/hooks/todo-continuation-enforcer/AGENTS.md +++ b/src/hooks/todo-continuation-enforcer/AGENTS.md @@ -38,7 +38,7 @@ session.idle ## CONSTANTS ```typescript -DEFAULT_SKIP_AGENTS = ["prometheus", "compaction"] +DEFAULT_SKIP_AGENTS = ["prometheus", "compaction", "plan"] CONTINUATION_COOLDOWN_MS = 30_000 // 30s between injections MAX_CONSECUTIVE_FAILURES = 5 // Then 5min pause (exponential backoff) FAILURE_RESET_WINDOW_MS = 5 * 60_000 // 5min window for failure reset diff --git a/src/hooks/todo-continuation-enforcer/constants.ts b/src/hooks/todo-continuation-enforcer/constants.ts index a26c7bc09..2b1c7f198 100644 --- a/src/hooks/todo-continuation-enforcer/constants.ts +++ b/src/hooks/todo-continuation-enforcer/constants.ts @@ -2,7 +2,7 @@ import { createSystemDirective, SystemDirectiveTypes } from "../../shared/system export const HOOK_NAME = "todo-continuation-enforcer" -export const DEFAULT_SKIP_AGENTS = ["prometheus", "compaction"] +export const DEFAULT_SKIP_AGENTS = ["prometheus", "compaction", "plan"] export const CONTINUATION_PROMPT = `${createSystemDirective(SystemDirectiveTypes.TODO_CONTINUATION)} diff --git a/src/hooks/todo-continuation-enforcer/continuation-injection.test.ts b/src/hooks/todo-continuation-enforcer/continuation-injection.test.ts index 0b628ca1e..f43a7c22d 100644 --- a/src/hooks/todo-continuation-enforcer/continuation-injection.test.ts +++ b/src/hooks/todo-continuation-enforcer/continuation-injection.test.ts @@ -47,4 +47,38 @@ describe("injectContinuation", () => { expect(capturedTools).toEqual({ question: false, bash: true }) expect(capturedText).toContain(OMO_INTERNAL_INITIATOR_MARKER) }) + + test("skips injection when agent is plan (prevents Plan Mode infinite loop)", async () => { + // given + let injected = false + const ctx = { + directory: "/tmp/test", + client: { + session: { + todo: async () => ({ data: [{ id: "1", content: "todo", status: "pending", priority: "high" }] }), + promptAsync: async () => { + injected = true + return {} + }, + }, + }, + } + const sessionStateStore = { + getExistingState: () => ({ inFlight: false, lastInjectedAt: 0, consecutiveFailures: 0 }), + } + + // when + await injectContinuation({ + ctx: ctx as never, + sessionID: "ses_plan_skip", + resolvedInfo: { + agent: "plan", + model: { providerID: "anthropic", modelID: "claude-sonnet-4-20250514" }, + }, + sessionStateStore: sessionStateStore as never, + }) + + // then + expect(injected).toBe(false) + }) })