feat(loop-prevention): detect and escalate infinite clarification loops#3683
feat(loop-prevention): detect and escalate infinite clarification loops#3683gutierrezx7 wants to merge 8 commits intocode-yeongyu:devfrom
Conversation
…responses Detects when an agent asks for more instructions in plain text (not using the question tool), which the existing pending-question-detection misses. Uses 20 regex patterns covering common patterns like 'I need more details', 'please clarify', 'what should I do', 'blocked on user input', etc. Includes comprehensive test suite with 34 test cases covering positive matches, false negatives, question tool coexistence, and real-world NETBOX MCP CRUD scenario. Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-openagent) Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
…eloads
Saves critical loop prevention counters (consecutiveClarifications, consecutiveFailures, stagnationCount) to .sisyphus/loop-state/{sessionID}.json. Prevents fresh-start loop resets when the plugin is reloaded — state survives restarts.
Integrated into SessionStateStore for automatic load on session init and persist on state mutations.
Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-openagent)
Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
Adds SessionState fields for tracking clarification loops: consecutiveClarifications, lastClarificationDetectedAt, inFlightSince. Adds escalation tiers: - CLARIFICATION_ESCALATION_PROMPT: tier 2 warning (take action, don't ask again) - CLARIFICATION_BLOCKED_PROMPT: tier 3 hard stop (cancel all tasks, write summary) Adds constants: MAX_CLARIFICATION_CONSECUTIVE (3), CLARIFICATION_COOLDOWN_MS (10s), IN_FLIGHT_TIMEOUT_MS (30s). Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-openagent) Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
Wires the clarification detection into the todo-continuation-enforcer decision gate with a 3-tier escalation: Tier 1 (1st clarification): allow normal continuation (agent might resolve) Tier 2 (2nd): inject CLARIFICATION_ESCALATION_PROMPT (warn to take action) Tier 3 (3rd+): inject CLARIFICATION_BLOCKED_PROMPT (hard stop, cancel todos) Changes: - idle-event.ts: adds clarification detection after pending-question check, escalation logic after resolvedInfo - handler.ts: calls setDirectory for persistence - continuation-injection.ts: accepts promptOverride, tracks inFlightSince, persists loop state - countdown.ts: passes promptOverride through countdown chain - session-state.ts: loads persisted state on init, persists on mutations, recovers stuck inFlight (30s timeout) Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-openagent) Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
…ypes Removes the Gemini/Minimax model restriction from isUnstableTask(). The babysitter's existing 2-minute idle timeout already prevents false positives, so expanding to all model types ensures loop behavior is caught regardless of which model is running. Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-openagent) Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
…ing todo list, state expiry Three bugs found during code review: 1. resetContinuationProgress() did not reset consecutiveClarifications — after all todos completed, the counter persisted and could trigger premature escalation on a new task session. 2. BLOCKED prompt injection used promptOverride which REPLACED the entire prompt, including the todo list. The agent was told to cancel all remaining tasks but had no visibility into what tasks remained. 3. loadLoopState() had no time threshold — persisted loop state from hours/days ago could trigger immediate escalation after a plugin restart. Added 1-hour expiry on persisted state. Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-openagent) Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
… write-permission Three bugs found in deep review: 1. BLOCKED injection was positioned AFTER the consecutiveFailures check (line 177). When failures hit MAX_CONSECUTIVE_FAILURES (5), the function returned before reaching the escalation logic. The BLOCKED prompt could never fire until the 5-min cooldown expired. Fix: Moved BLOCKED check to immediately after the inFlight recovery check, BEFORE the failures check. This ensures the one-way BLOCKED instruction is always delivered, even when regular continuation is failing. 2. injectContinuation had a write-permission gate that blocked BLOCKED injection silently. Since BLOCKED uses void (fire-and-forget), idle-event.ts already returned and never learned the injection was skipped. The agent was stuck in limbo: no continuation prompts, no BLOCKED instruction. Fix: hasWritePermission check now allows promptOverride to bypass it. BLOCKED/WARNING prompts are system instructions, not implementation work — they must be delivered regardless of write tool permissions. 3. Also removed the now-redundant duplicate BLOCKED check from its old position (after resolvedInfo) since it moved earlier in the flow. Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-openagent) Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
…anguages) Expanded clarification-seeking patterns from English-only to 11 languages covering the top 10 most spoken languages worldwide: - English (21 patterns, existing) - Portuguese pt-BR (16 patterns) - Spanish (16 patterns) - French (16 patterns) - German (16 patterns) - Japanese (15 patterns, CJK) - Korean (14 patterns, Hangul) - Chinese Mandarin (15 patterns, Hanzi) - Russian (16 patterns, Cyrillic) - Hindi (14 patterns, Devanagari) - Arabic (15 patterns, Arabic script) Total: ~174 patterns across Latin, CJK, Cyrillic, Devanagari, and Arabic scripts. Added 10 multilingual test cases (one per additional language) for regression coverage. Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-openagent) Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
b6b16ce to
832bd33
Compare
There was a problem hiding this comment.
5 issues found across 18 files
Confidence score: 2/5
- Merge risk is high because there are multiple medium-to-high severity issues (6–7/10) with strong confidence, including security-sensitive behavior changes.
- Most severe:
src/hooks/todo-continuation-enforcer/loop-state-persistence.tsbuilds filesystem paths from an unsanitizedsessionID, which can enable path traversal outside.sisyphus/loop-state. src/hooks/todo-continuation-enforcer/continuation-injection.tsappears to broaden prompt-override write bypasses beyond blocked-mode enforcement, potentially bypassing escalation-related permission checks;src/hooks/todo-continuation-enforcer/handler.tsmay also miss persisted state when sessions are created beforesession.idle.- Pay close attention to
src/hooks/todo-continuation-enforcer/loop-state-persistence.ts,src/hooks/todo-continuation-enforcer/continuation-injection.ts,src/hooks/todo-continuation-enforcer/handler.ts,src/hooks/todo-continuation-enforcer/clarification-detection.ts- security-sensitive path handling, permission bypass scope, persistence initialization ordering, and clarification-turn parsing correctness.
Prompt for AI agents (unresolved issues)
Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.
<file name="src/hooks/todo-continuation-enforcer/continuation-injection.ts">
<violation number="1" location="src/hooks/todo-continuation-enforcer/continuation-injection.ts:151">
P2: Write-permission bypass is too broad: any prompt override now bypasses tool permission checks, including escalation warning prompts, not just blocked-mode enforcement.</violation>
</file>
<file name="src/hooks/todo-continuation-enforcer/handler.ts">
<violation number="1" location="src/hooks/todo-continuation-enforcer/handler.ts:64">
P2: Session-state persistence directory is initialized only on `session.idle`, so earlier `getState()` calls can instantiate sessions before persistence is configured, skipping persisted state load for that session.</violation>
</file>
<file name="src/hooks/todo-continuation-enforcer/loop-state-persistence.ts">
<violation number="1" location="src/hooks/todo-continuation-enforcer/loop-state-persistence.ts:17">
P1: Unsanitized `sessionID` is used in filesystem path construction, enabling potential path traversal outside `.sisyphus/loop-state`.</violation>
</file>
<file name="src/hooks/todo-continuation-enforcer/clarification-detection.ts">
<violation number="1" location="src/hooks/todo-continuation-enforcer/clarification-detection.ts:184">
P3: Duplicate regex pattern in the Hindi clarification-detection block causes redundant matching and future maintenance drift.</violation>
<violation number="2" location="src/hooks/todo-continuation-enforcer/clarification-detection.ts:243">
P2: Assistant messages without `parts` are skipped instead of treated as the terminal latest assistant turn, allowing stale older messages to drive clarification-loop detection.</violation>
</file>
Reply with feedback, questions, or to request a fix. Tag @cubic-dev-ai to re-run a review.
| } | ||
|
|
||
| function getStatePath(directory: string, sessionID: string): string { | ||
| return join(directory, LOOP_STATE_DIR, `${sessionID}.json`) |
There was a problem hiding this comment.
P1: Unsanitized sessionID is used in filesystem path construction, enabling potential path traversal outside .sisyphus/loop-state.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At src/hooks/todo-continuation-enforcer/loop-state-persistence.ts, line 17:
<comment>Unsanitized `sessionID` is used in filesystem path construction, enabling potential path traversal outside `.sisyphus/loop-state`.</comment>
<file context>
@@ -0,0 +1,97 @@
+}
+
+function getStatePath(directory: string, sessionID: string): string {
+ return join(directory, LOOP_STATE_DIR, `${sessionID}.json`)
+}
+
</file context>
| } | ||
|
|
||
| if (!hasWritePermission(tools)) { | ||
| if (!hasWritePermission(tools) && !promptOverride) { |
There was a problem hiding this comment.
P2: Write-permission bypass is too broad: any prompt override now bypasses tool permission checks, including escalation warning prompts, not just blocked-mode enforcement.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At src/hooks/todo-continuation-enforcer/continuation-injection.ts, line 151:
<comment>Write-permission bypass is too broad: any prompt override now bypasses tool permission checks, including escalation warning prompts, not just blocked-mode enforcement.</comment>
<file context>
@@ -145,14 +148,15 @@ export async function injectContinuation(args: {
}
- if (!hasWritePermission(tools)) {
+ if (!hasWritePermission(tools) && !promptOverride) {
log(`[${HOOK_NAME}] Skipped: agent lacks write permission`, { sessionID, agent: agentName })
return
</file context>
| const sessionID = props?.sessionID as string | undefined | ||
| if (!sessionID) return | ||
|
|
||
| sessionStateStore.setDirectory(ctx.directory) |
There was a problem hiding this comment.
P2: Session-state persistence directory is initialized only on session.idle, so earlier getState() calls can instantiate sessions before persistence is configured, skipping persisted state load for that session.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At src/hooks/todo-continuation-enforcer/handler.ts, line 64:
<comment>Session-state persistence directory is initialized only on `session.idle`, so earlier `getState()` calls can instantiate sessions before persistence is configured, skipping persisted state load for that session.</comment>
<file context>
@@ -61,6 +61,7 @@ export function createTodoContinuationHandler(args: {
const sessionID = props?.sessionID as string | undefined
if (!sessionID) return
+ sessionStateStore.setDirectory(ctx.directory)
sessionStateStore.startPruneInterval()
await handleSessionIdle({
</file context>
|
|
||
| if (role === "user") return { isAskingForClarification: false } | ||
|
|
||
| if (role === "assistant" && msg.parts) { |
There was a problem hiding this comment.
P2: Assistant messages without parts are skipped instead of treated as the terminal latest assistant turn, allowing stale older messages to drive clarification-loop detection.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At src/hooks/todo-continuation-enforcer/clarification-detection.ts, line 243:
<comment>Assistant messages without `parts` are skipped instead of treated as the terminal latest assistant turn, allowing stale older messages to drive clarification-loop detection.</comment>
<file context>
@@ -0,0 +1,287 @@
+
+ if (role === "user") return { isAskingForClarification: false }
+
+ if (role === "assistant" && msg.parts) {
+ const hasQuestionTool = msg.parts.some(
+ (part) =>
</file context>
| // ── हिन्दी (HINDI) ────────────────────────────────────── | ||
| /\u092E\u0941\u091D\u0947 \u0914\u0930 (जानकारी|विवरण|निर्देश|स्पष्टीकरण|मार्गदर्शन|विशिष्टताएं|संदर्भ) (चाहिए|की जरूरत है)/i, | ||
| /\u0915\u0943\u092A\u092F\u093E[, ]?(स्पष्ट करें|समझाएं|निर्दिष्ट करें|बताएं|विस्तृत करें|परिभाषित करें|प्रदान करें)/i, | ||
| /\u092E\u0948\u0902 (क्या करूं|क्या करना चाहिए|क्या कर सकता हूं)/i, |
There was a problem hiding this comment.
P3: Duplicate regex pattern in the Hindi clarification-detection block causes redundant matching and future maintenance drift.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At src/hooks/todo-continuation-enforcer/clarification-detection.ts, line 184:
<comment>Duplicate regex pattern in the Hindi clarification-detection block causes redundant matching and future maintenance drift.</comment>
<file context>
@@ -0,0 +1,287 @@
+ // ── हिन्दी (HINDI) ──────────────────────────────────────
+ /\u092E\u0941\u091D\u0947 \u0914\u0930 (जानकारी|विवरण|निर्देश|स्पष्टीकरण|मार्गदर्शन|विशिष्टताएं|संदर्भ) (चाहिए|की जरूरत है)/i,
+ /\u0915\u0943\u092A\u092F\u093E[, ]?(स्पष्ट करें|समझाएं|निर्दिष्ट करें|बताएं|विस्तृत करें|परिभाषित करें|प्रदान करें)/i,
+ /\u092E\u0948\u0902 (क्या करूं|क्या करना चाहिए|क्या कर सकता हूं)/i,
+ /\u0915\u094C\u0928 \u0938\u093E (API|एंडपॉइंट|फ़ील्ड|पैरामीटर|तरीका|विकल्प)/i,
+ /(आपके|आपकी|आपका) (उत्तर|प्रतिक्रिया|फीडबैक|निर्णय|राय|जवाब) (का इंतजार है|प्रतीक्षा है|इंतजार कर रहा हूं)/i,
</file context>
Summary
questiontool) and prevent infinite continuation loops.Changes
New modules
clarification-detection.ts— 174 regex patterns across 11 languages detecting plain-text clarification requestsloop-state-persistence.ts— Save loop counters to.sisyphus/loop-state/{sessionID}.jsonwith 1-hour expiryModified modules
idle-event.ts— Insert clarification detection after pending-question check; BLOCKED check runs before consecutiveFailures gatetypes.ts— AddconsecutiveClarifications,lastClarificationDetectedAt,inFlightSinceconstants.ts— Add escalation prompts (WARNING + BLOCKED),MAX_CLARIFICATION_CONSECUTIVE(3), cooldown (10s), in-flight timeout (30s)continuation-injection.ts— AcceptpromptOverride, trackinFlightSince, persist loop state on failure/success; BLOCKED bypasses write-permission gatecountdown.ts— ThreadpromptOverridethrough countdown chainhandler.ts— CallsetDirectoryfor persistencesession-state.ts— Load persisted state on init, persist on mutations, recover stuck inFlight (30s timeout), reset clarification counters on progresstask-message-analyzer.ts— Expand babysitter to all task types (not just Gemini/Minimax)Escalation tiers
Testing
bun test src/hooks/todo-continuation-enforcer/— 132 tests ✅ (incl. clarity detection, persistence, stagnation, integration)bun run typecheck✅bun run build✅Related Issues
N/A — feature request.
Need help on this PR? Tag
@codesmithwith what you need.Summary by cubic
Prevents infinite “please clarify” loops by detecting plain‑text clarification requests and escalating to a hard stop, with state persisted across restarts. Also adds native
deepseek-v4support with an optimized Sisyphus prompt and routing.New Features
consecutiveClarifications, applies a 10s cooldown, and times out stuck in‑flight injections after 30s..sisyphus/loop-state/{sessionID}.jsonwith a 1‑hour expiry; auto‑load/save via the session store so restarts don’t reset counters.deepseek-v4support: model detection (Pro/Flash/R1), V4‑specific Sisyphus prompt, enable thinking for Pro, and add to Sisyphus/Oracle fallback chains.Bug Fixes
Written for commit 832bd33. Summary will update on new commits. Review in cubic