-
Notifications
You must be signed in to change notification settings - Fork 13
Description
Severity: Fatal
Category: Memory Leak
Files: src/plan-orchestrator.ts
Description
Both runResearchAgent() and runPlannerAgent() create standalone Session objects and call runPrompt(). When runPrompt() throws, the catch block removes the session from runningSessions but never calls session.stop().
The Session class registers listener closures on its internal _taskTracker and _ralphTracker objects during construction (lines 437–461 of session.ts). These closures are only removed by cleanupTrackerListeners(), which is only ever called from session.stop(). Without stop(), the closures remain attached and no explicit resource cleanup runs — relying entirely on the garbage collector to eventually reclaim the session.
Reproduction
- Enable the Plan Orchestrator and trigger a plan generation.
- While Claude is running, cause
runPrompt()to fail (e.g. kill the Claude CLI process, or simulate a network error). - Repeat several times.
- Inspect memory usage — Session objects accumulate with each failed agent run.
Code
src/plan-orchestrator.ts, research agent catch block (~line 455):
} catch (err) {
this.runningSessions.delete(session);
// session.stop() is never called
const durationMs = Date.now() - startTime;
const error = err instanceof Error ? err.message : String(err);
onSubagent?.({ type: 'failed', ... });
return { success: false, ... };
} finally {
clearInterval(progressInterval);
// only the interval is cleaned up — not the session
}The same pattern exists in runPlannerAgent() (~line 533).
Note: cancel() correctly calls session.stop() via its own loop, but the per-agent error paths do not.
Impact
Each failed plan agent run leaves a Session object whose cleanup is non-deterministic. Sessions hold terminal buffers up to 2MB each. During high-frequency plan generation with repeated failures, many of these can be in-flight simultaneously, causing heap pressure that delays GC cycles. The fragile reliance on GC also means a single future change to session initialization can silently turn a temporary pressure issue into a permanent leak.