Skip to content

fix: tool_output timers leak when session switches to background #73

Description

@jdmanring

Summary

tool_start starts two intervals on currentToolBubble: _waveInterval (100 ms) and _elapsedTicker (50 ms). The only cleanup point is tool_output, but tool_output opens with if (_isBg) continue; — a background session switch skips the rest of the handler, leaving both intervals running against a detached node indefinitely.

A second related bug: _evictLive can remove the node currentToolBubble points to before tool_output arrives. Subsequent writes to the detached node silently fail.

Proposed Fix

In static/js/chat.js, move timer cleanup unconditionally before the _isBg guard, and add an isConnected guard after it:

} else if (json.type === 'tool_output') {
  // Timer cleanup runs unconditionally — tool_start always starts these
  // and this is the only reliable cleanup point; the background path
  // skips the rest of this handler.
  if (currentToolBubble) {
    if (currentToolBubble._waveInterval) {
      clearInterval(currentToolBubble._waveInterval);
      currentToolBubble._waveInterval = null;
    }
    if (currentToolBubble._elapsedTicker) {
      clearInterval(currentToolBubble._elapsedTicker);
      currentToolBubble._elapsedTicker = null;
    }
  }
  if (_isBg) continue;
  if (!currentToolBubble || !currentToolBubble.isConnected) {
    console.warn('[chatHistory] tool_output: bubble evicted before completion');
    continue;
  }
  // ... rest of handler

Impact

Leaking intervals keep JS alive and prevent GC of the bubble node (holds DOM ref + closures over header/wave/content element refs). In long agentic sessions with many tool calls, this compounds the RSS growth documented in the OOM investigation. The isConnected guard prevents silent data loss when a bubble is evicted by _evictLive mid-flight.

Context

Identified during the senior review pass of the Oilpan OOM investigation. Research doc: docs/fork/memory-explosion-research.md.

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't working

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions