Repository navigation
feat: Add Error Interception Middleware for guided AI self-correcting tool errors #1000
Description
Problem
When the AI model makes structural mistakes (e.g., passing cwd as an object instead of a string, nesting tool calls inside parameters, or emitting XML tool markup alongside native tool calls), Zoo Code currently:
- Shows raw error messages that the AI cannot parse or learn from
- Repeats the same mistake until
consecutiveMistakeLimitis reached - Displays an interactive "Continue" button that freezes the workflow until the user manually intervenes
This affects all models but is especially prevalent with MiMo models, where ~80% of diagnostics show CWD_OBJECT_MISUSE as the dominant failure mode.
Related Issues
This middleware addresses or provides infrastructure for the following existing bug reports:
Directly Covered by This Middleware
| Issue | Title | Pattern Coverage | How Middleware Helps |
|---|---|---|---|
| #752 | Shell Integration Warning when using MiMo | SHELL_INTEGRATION/001 |
When shell integration fails, middleware returns a guided message telling the AI to use non-shell tools instead of looping. Prevents the 10+ consecutive failure spiral described in the issue. |
| #704 | Shell Integration Error persists after long sessions | SHELL_INTEGRATION/001 |
Same as #752. The dead terminal reuse infinite loop is mitigated by the MODEL_STUCK_LOOP circuit breaker — after 3 failures, the AI is directed to change strategy entirely. |
| #800 | Terminal cannot capture results, dialog stuck | MODEL_STUCK_LOOP |
The circuit breaker prevents the "stuck at Running" state by resetting consecutiveMistakeCount and directing the AI to try a different approach after 3 occurrences. |
| #794 | Errors Produced by Open Weight Models + Solutions | All patterns | This issue's community patches (Guide G1/G2/G3, XML fallback P1) are the direct inspiration for this middleware. Our middleware formalizes the "guide the AI" philosophy into official, testable infrastructure with 11+1 categorized patterns instead of minified extension.js patches. |
| #547 | DeepSeek 5 structural tool-calling defects | PARAM_MISSING, XML_NATIVE_DUAL_PROTOCOL, CONTEXT_OVERFLOW |
Defect 1 (Missing Required Parameters) → PARAM_MISSING/001 pattern. Defect 4 (XML Tag Fallback) → XML_NATIVE_DUAL_PROTOCOL pattern strips XML and guides to native tool_use. Defect 3 (reasoning_content loss) → CONTEXT_OVERFLOW/001 auto-recovery. |
| #709 | Tool call serialization failure - XML output | XML_NATIVE_DUAL_PROTOCOL |
When mimo-v2.5-pro outputs XML tags alongside native calls, middleware strips the XML from visible text and merges a protocol guide into the native tool's result. The AI sees "Use native tool_use only" and self-corrects. |
| #707 | Chat output truncation at 8KB - XML tool calls as raw text | XML_NATIVE_DUAL_PROTOCOL |
Same pattern as #709. Middleware detects XML markers in text blocks and strips them before rendering. Combined with the 10,000-char text-length guard to prevent over-stripping on very long responses. |
| #695 | Streaming tool-call argument deltas silently dropped | PARAM_MISSING/001 |
When streaming data loss causes missing parameters, the PARAM_MISSING pattern returns a guided message with the tool's required parameters and correct usage example, instead of a cryptic "missing nativeArgs" error. |
| #459 | Claude model proxy errors/spinning | MODEL_STUCK_LOOP |
When the model enters an error loop, the circuit breaker at occurrence 3 resets consecutiveMistakeCount and issues a [MODEL_STUCK_LOOP] directive. The interactive "Continue" button is suppressed — the AI self-corrects. |
| #458 | Requests keep spinning | MODEL_STUCK_LOOP |
Same circuit breaker mechanism. Prevents indefinite spinning by directing the AI to change strategy. |
Indirectly Related (Middleware Provides Partial Coverage)
| Issue | Title | Relationship |
|---|---|---|
| #648 | Un-controllable Token Exceed with MCP results | CONTEXT_OVERFLOW/001 pattern provides auto-recovery guidance when context window is exceeded. Does not prevent the overflow itself, but guides the AI to compress or continue from summary. |
| #427 | Shell-integration polling refactor | SHELL_INTEGRATION/001 handles the error gracefully when shell integration fails, regardless of the underlying polling mechanism. Complementary fixes. |
| #471 | Infinite JSON tool loop on Ollama | MODEL_STUCK_LOOP circuit breaker prevents infinite loops. Does not fix the Ollama tool-calling detection issue itself. |
Not Covered (Outside Middleware Scope)
| Issue | Reason |
|---|---|
| #257 | Chat stuck on "API Request" — UI state management issue, not tool error |
| #877 | Ollama model refresh stuck — settings panel UI issue |
| #937 | Message processing order at terminal finish — task lifecycle issue |
| #856 | Task Tree Abstraction — architecture enhancement |
| #464 | Universal AI Clipboard — new feature proposal |
Originally, you had to click the 'Proceed Anyway' button to proceed in this state, but I changed it to a format that guides the AI to perform the correct task, as shown below.
Solution: Error Interception Middleware
A new middleware layer that intercepts, classifies, and transforms tool errors into actionable guidance messages, enabling the AI to self-correct without user intervention.
Architecture
Tool Error → ErrorClassifier → ErrorPattern DB (11+1 categories)
↓
MessageTransformer → Versioned JSON guidance
↓
TaskErrorState (WeakMap) → Circuit Breaker
↓
Exactly one guided tool_result → AI self-corrects
New Modules
| Module | Purpose |
|---|---|
errorPatterns.ts |
Pattern DB with 11+1 error categories and variant entries |
ErrorClassifier.ts |
Deterministic error classifier (priority-ordered pattern matching) |
MessageTransformer.ts |
Versioned JSON guidance serializer (WHAT/WHY/NEXT format) |
TaskErrorState.ts |
Task-scoped persistent state via WeakMap (occurrence tracking, circuit breaker) |
StructuralValidator.ts |
CWD object detection + nested parameter overflow detection |
ToolErrorInterceptor.ts |
Interceptor facade (transformError, circuit status) |
Error Patterns Covered
| # | Category | Pattern ID | Trigger | Behavior | Related Issues |
|---|---|---|---|---|---|
| 1 | DUPLICATE_CALL | EI/DUPLICATE_CALL/001 | Repeated identical tool call | Block + guide | — |
| 2 | PARAM_MISSING | EI/PARAM_MISSING/001 | Required parameter missing | Block + guide with example | #695, #547 Defect 1 |
| 3 | PARAM_TYPE_MISMATCH | EI/PARAM_TYPE_MISMATCH/001 | Generic type mismatch | Block + guide | — |
| 4 | PARAM_TYPE_MISMATCH | EI/PARAM_TYPE_MISMATCH/002 | CWD passed as object | Block + guide "cwd must be a string" | — |
| 5 | PARAM_TYPE_MISMATCH | EI/PARAM_TYPE_MISMATCH/003 | Nested tool invocation in param | Block + guide "issue separate calls" | — |
| 6 | FILE_NOT_FOUND | EI/FILE_NOT_FOUND/001 | File/directory not found | Guide to use list_files | — |
| 7 | SHELL_INTEGRATION | EI/SHELL_INTEGRATION/001 | Shell integration failure | Guide to use non-shell tools | #752, #704, #427 |
| 8 | DIFF_MATCH_FAILED | EI/DIFF_MATCH_FAILED/001 | apply_diff near-miss (fuzzy match) | Correct + retry with matched content | — |
| 9 | MCP_TOOL_MISSING | EI/MCP_TOOL_MISSING/001 | MCP tool/server not found | Guide to select from list | — |
| 10 | INVALID_TOOL_PROTOCOL | EI/INVALID_TOOL_PROTOCOL/001 | XML tool calls in text | Strip XML + guide | #709, #707 |
| 11 | INVALID_TOOL_PROTOCOL | EI/INVALID_TOOL_PROTOCOL/002 | XML + native dual protocol | Strip XML + merge protocol guide | #709, #707, #547 Defect 4 |
| 12 | INVALID_JSON_ARGUMENTS | EI/INVALID_JSON_ARGUMENTS/001 | Concatenated JSON objects in tool call arguments | Guide to issue one tool call per object | — |
| 13 | CONTEXT_OVERFLOW | EI/CONTEXT_OVERFLOW/001 | Context window exceeded | Auto-recovery guide | #648, #547 Defect 3 |
| 14 | MODEL_STUCK_LOOP | (circuit breaker) | Same error 3x | Strategy change directive, no user prompt | #800, #459, #458, #752, #704 |
Circuit Breaker (MODEL_STUCK_LOOP)
| Occurrence | Behavior |
|---|---|
| 1 | Guided correction (variant-specific message) |
| 2 | Strengthened guidance ("Re-read the tool schema now") |
| 3 | Circuit opens — [MODEL_STUCK_LOOP] directive, consecutiveMistakeCount reset, no user prompt |
Design Philosophy (from #794)
"We have two options: (1) Suppress error messages and let the AI self-correct, (2) When the AI exhibits a bad habit that causes errors, guide it toward the correct approach. We prefer option 2."
This middleware implements option 2 as official, testable infrastructure. Each error produces a WHAT/WHY/NEXT guidance payload that tells the AI:
- WHAT went wrong (specific, not generic)
- WHY it happened (root cause, not symptom)
- NEXT what to do (actionable steps, not vague advice)
Before vs After
| Scenario | Before | After |
|---|---|---|
AI sends cwd: {command: "..."} |
Terminal error → raw message → AI repeats → user must click Continue | Preflight blocks → guided tool_result with "cwd must be a string" → AI self-corrects |
| AI emits XML + native tool call | Confusing partial execution → user must intervene (#709, #707) | XML stripped, native executes, protocol guide merged |
| AI nests parallel params in one call | Cryptic error → user must Continue | Preflight detects nested signature → guided "issue separate calls" |
| Same mistake 3x | consecutiveMistakeLimit → interactive prompt → user must click (#800, #459) |
Circuit breaker → [MODEL_STUCK_LOOP] → AI changes strategy |
| Shell integration failure | Raw error → AI retries same terminal → infinite loop (#752, #704) | SHELL_INTEGRATION/001 guide → AI uses non-shell tools |
| AI concatenates multiple JSON objects in one tool call | JSON.parse error → tool call silently discarded → model retries same mistake | INVALID_JSON_ARGUMENTS guide → "Issue one tool call per file" → AI self-corrects |
Test Coverage
- 144 tests across 7 test files, all passing
- StructuralValidator: CWD detection, nested param detection, allow-list, cycle detection
- TaskErrorState: Occurrence tracking, circuit open/close, fingerprint, reset, WeakMap isolation
- presentAssistantMessage: 7 integration tests (CWD blocked, circuit at occurrence 3, nested overflow, XML+native, etc.)
- model-stuck-loop: 10 tests (accumulation, threshold, suppression, per-Task isolation)
Files Changed
| File | Type | Description |
|---|---|---|
src/core/tools/error-interception/errorPatterns.ts |
Modified | 3 variant entries (CWD, NESTED, XML_DUAL) |
src/core/tools/error-interception/TaskErrorState.ts |
New | Task-scoped persistent state (WeakMap) |
src/core/tools/error-interception/StructuralValidator.ts |
New | CWD + nested param detection |
src/core/assistant-message/presentAssistantMessage.ts |
Modified | Structural preflight + XML detection + FP-1 fix (classify errors by message content) |
src/core/task/Task.ts |
Modified | MODEL_STUCK_LOOP circuit breaker |
src/core/tools/error-interception/index.ts |
Modified | Stable exports |
| Test files (4 new) | New | 144 tests total |
Commits
fc38100a6— feat: complete gap-fix for 4 uncovered error patterns (+1460/-2)291f4594b— fix: resolve false positive PARAM_TYPE_MISMATCH on validateToolUse failures (+13/-1)429fb9f44— feat: add INVALID_JSON_ARGUMENTS pattern for concatenated JSON objects (+208/-13)
Update: INVALID_JSON_ARGUMENTS Pattern (260725)
Based on edelauna's contribution, a new pattern was added to cover the case where AI models concatenate multiple JSON objects into a single tool call's arguments string (e.g., {"path":"file1.ts"}{"path":"file2.ts"}). This causes JSON.parse to throw in NativeToolCallParser.parseToolCall(), and the tool call is silently discarded without feedback.
New pattern: EI/INVALID_JSON_ARGUMENTS/001 (priority 63)
- Trigger:
JSON.parsefailure on tool call arguments - WHAT: "Tool call arguments could not be parsed as JSON."
- WHY: "You concatenated multiple JSON objects into a single arguments string. Each tool call must contain exactly one valid JSON object."
- NEXT: "Issue one tool call per file. Use parallel tool calls if you need multiple files simultaneously."
Replaces closed #999 (could not add labels due to repository permissions).
Activity
edelauna commentedon Jul 25, 2026
Additional failure pattern observed: concatenated JSON objects in a single tool call
During local testing of the TaskRegistry migration (Story 3.2a), a child task triggered two consecutive parse failures in
NativeToolCallParser.parseToolCall(line 701):The logged
argumentsstring showed 6 separate JSON objects concatenated without a separator:{"path":"src/core/task/apiConversationHistory.ts","mode":"slice",...}{"path":"src/core/task/AskIgnoredError.ts","mode":"slice",...}{"path":"src/core/task/build-tools.ts",...}...The model attempted to batch multiple
read_filecalls into one tool call by concatenating their argument objects — a syntax that doesn't exist. The tool description explicitly says "This tool reads exactly one file per call. If you need multiple files, issue multiple parallel read_file calls.", so the model should have issued 6 separate tool calls. After the first failure, it retried with 4 files and hit the same error.How this interacts with the middleware:
This pattern isn't covered by the current 12 patterns listed above.
JSON.parsethrows before any argument validation can run, soparseToolCallreturnsnull— the tool call is silently dropped. The model receives no feedback about what went wrong, which is why it retried with a slightly different (but still malformed) batch instead of splitting into individual calls.A new pattern along the lines of
INVALID_JSON_ARGUMENTS(or extendingPARAM_MISSINGto cover the parse-failure case) would let the middleware return something like:The parse failure happens at
NativeToolCallParser.ts:701— before theswitchblock that the middleware currently hooks into — so the intercept point would need to be at thenullreturn fromparseToolCall, or the caller inpresentAssistantMessagethat currently discardsnullsilently.feat(error-interception): add INVALID_JSON_ARGUMENTS pattern for conc…
feat(error-interception): add deterministic error interception middle…
feat(error-interception): add INVALID_JSON_ARGUMENTS pattern for conc…
feat(error-interception): add deterministic error interception middle…
feat(error-interception): add deterministic error interception middle…
feat(error-interception): add INVALID_JSON_ARGUMENTS pattern for conc…
feat(error-interception): add deterministic error interception middle…
feat(error-interception): add INVALID_JSON_ARGUMENTS pattern for conc…
feat(error-interception): add deterministic error interception middle…
feat(error-interception): add INVALID_JSON_ARGUMENTS pattern for conc…