Bugfix: Session 135: MCP outcome({ action: 'report' }) throws 'Maxim...
Documentation for Sessions
🐛 Bugfix: Session 135: MCP outcome({ action: 'report' }) throws 'Maxim...
Date: 2026-02-22
Time: 20:04:27 UTC
Session: gspace1-135.9
Agent: storage
Confidence: 97%
Status: ✅ Documented
📋 Decision Context
Question:
Session 135: MCP outcome({ action: 'report' }) throws 'Maximum call stack size exceeded' — decision-level outcome tracking completely broken
Task: Fix MCP outcome report stack overflow
🎯 Decision Made
Chosen:
Remove mutual recursion by making decisionLifecycleManager.reportOutcome() only update lifecycle state, not call handleReportOutcome() back. Also fixed secondary bug: decision.timestamp.getTime() fails because timestamps deserialized from JSON are strings, not Date objects — wrapped in new Date().
Options Considered:
- Add recursion guard flag to prevent re-entry
- Remove the mutual recursion by making lifecycle manager not call back into report-outcome
- Merge reportOutcome logic into the lifecycle manager to avoid cross-calls
- Add try-catch around the lifecycle call and swallow the recursion error
💭 Reasoning
ROOT CAUSE: Direct mutual recursion between two files. handleReportOutcome() (report-outcome.ts:58) calls decisionLifecycleManager.reportOutcome(). decisionLifecycleManager.reportOutcome() (decision-lifecycle-manager.ts:102) calls handleReportOutcome() back. A calls B, B calls A → infinite stack → crash. The file even had a comment warning 'Do NOT call handleLogDecision from here - it creates infinite recursion' but the same pattern was applied to handleReportOutcome without anyone catching it. FIX: The lifecycle manager's reportOutcome() method should only update lifecycle state (set phase to COMPLETED, stop validation monitoring). The outcome data is already persisted by the caller (handleReportOutcome handles updateDecisionOutcome, saveMemoryStore, recordLearning). There's no reason for the lifecycle manager to re-invoke the full report flow. SECONDARY BUG: After fixing the recursion, a new error appeared: 'decision.timestamp.getTime is not a function'. When decisions are loaded from disk (JSON.parse), timestamp is a string like '2026-02-22T...', not a Date object. The super intelligence reflect call at line 89 called .getTime() on it. Fix: wrap in new Date(). WHY NOT recursion guard flag: Adds complexity and hides the architectural problem — the real issue is that two functions shouldn't call each other. WHY NOT try-catch swallow: Masks the bug instead of fixing it, and the outcome never gets recorded. WHAT WOULD MAKE THIS WRONG: If the lifecycle manager genuinely needed to trigger side effects that only handleReportOutcome knows about. It doesn't — all it needs is to mark the lifecycle as COMPLETED. EARLY WARNING: Any function that imports and calls its own caller is a recursion risk. The existing comment warned about handleLogDecision but missed handleReportOutcome — same pattern, same bug.
📁 Files Involved
MCP/mcp-gograb-ai/src/services/decision-lifecycle-manager.tsMCP/mcp-gograb-ai/src/tools/report-outcome.ts
🔗 Related
- Decision ID:
dev_1771790667674_gdnz716 - Session: 135
- Workspace: gspace1
Auto-generated by MCP Smart Consolidation