BUG-HUNT: [MCP-RESOURCE] Memory leak in MCPRefreshHook notification listener cleanup #7133

Open
opened 2026-04-10 08:05:22 +00:00 by HAL9000 · 1 comment
Owner

Bug Report: [RESOURCE] — Memory leak in MCPRefreshHook notification listener cleanup

Severity Assessment

  • Impact: Memory leaks in long-running systems, potential callback failures on destroyed objects
  • Likelihood: High in systems with frequent MCP server connection/disconnection cycles
  • Priority: Medium

Location

  • File: src/cleveragents/mcp/refresh_hook.py
  • Function/Class: MCPRefreshHook.__init__(), MCPRefreshHook.cancel()
  • Lines: 76, 125-137

Description

The MCPRefreshHook registers itself as a notification listener with the MCPToolAdapter but never removes itself, creating a memory leak where destroyed refresh hooks remain in the adapter's listener list.

Evidence

# In src/cleveragents/mcp/refresh_hook.py line 76:
def __init__(self, adapter: MCPToolAdapter, ...):
    # ...
    adapter.add_notification_listener(self._on_notification)  # Adds listener

# In cancel() method lines 125-137:
def cancel(self) -> None:
    """Cancel any pending debounced refresh."""
    with self._lock:
        if self._timer is not None:
            self._timer.cancel()
            self._timer = None
            # BUG: No removal of notification listener!

Problem scenarios:

  1. MCPRefreshHook object is created and registers with adapter
  2. Hook object goes out of scope / gets garbage collected
  3. Adapter still holds reference to self._on_notification method
  4. Adapter tries to call notification on destroyed object → potential failures
  5. Adapter's listener list grows without cleanup → memory leak

Expected Behavior

  • Refresh hooks should remove themselves as notification listeners when canceled/destroyed
  • Adapter's listener list should not accumulate references to destroyed objects
  • No memory leaks in long-running systems with hook lifecycle management

Actual Behavior

  • cancel() method only stops pending timers, doesn't clean up listener registration
  • No automatic cleanup when hook objects are destroyed
  • Adapter accumulates stale listener references over time
  • Potential callback failures on garbage-collected hook methods

Suggested Fix

  1. Add remove_notification_listener() method to MCPToolAdapter
  2. Call listener removal in cancel() method
  3. Add __del__ method to MCPRefreshHook for cleanup on destruction
  4. Consider weak references in notification listener storage to prevent leaks

Category

resource-management

TDD Note

After this bug issue is verified, a corresponding Type/Testing issue will be created for TDD. The test will use tags: @tdd_issue, @tdd_issue_, and @tdd_expected_fail to prove the bug exists before fixing it.


Metadata

  • Branch: fix/mcp-refresh-hook-notification-listener-cleanup
  • Commit Message: fix(mcp): remove notification listener in MCPRefreshHook.cancel() to prevent memory leak
  • Milestone: N/A (backlog — see note below)
  • Parent Epic: #7023

Backlog note: This issue was discovered during autonomous operation
on milestone v3.5.0. It does not block milestone completion and has been
placed in the backlog for human review and future milestone assignment.

Subtasks

  • Reproduce the leak with a focused Behave scenario (tag @tdd_issue + @tdd_expected_fail)
  • Add remove_notification_listener() method to MCPToolAdapter
  • Call adapter.remove_notification_listener(self._on_notification) in MCPRefreshHook.cancel()
  • Add __del__ method to MCPRefreshHook for cleanup on destruction
  • Evaluate weak reference approach for notification listener storage in MCPToolAdapter
  • Remove @tdd_expected_fail once the fix is in place and the test passes

Definition of Done

  • MCPToolAdapter exposes a remove_notification_listener() method
  • MCPRefreshHook.cancel() removes the notification listener from the adapter
  • MCPRefreshHook.__del__() (or equivalent) ensures cleanup on destruction
  • Behave scenario with @tdd_issue and @tdd_issue_<N> tags confirms the fix
  • No stale listener references remain after hook cancellation/destruction
  • All nox stages pass
  • Coverage >= 97%

Automated by CleverAgents Bot
Supervisor: Bug Hunt | Agent: new-issue-creator

## Bug Report: [RESOURCE] — Memory leak in MCPRefreshHook notification listener cleanup ### Severity Assessment - **Impact**: Memory leaks in long-running systems, potential callback failures on destroyed objects - **Likelihood**: High in systems with frequent MCP server connection/disconnection cycles - **Priority**: Medium ### Location - **File**: `src/cleveragents/mcp/refresh_hook.py` - **Function/Class**: `MCPRefreshHook.__init__()`, `MCPRefreshHook.cancel()` - **Lines**: 76, 125-137 ### Description The MCPRefreshHook registers itself as a notification listener with the MCPToolAdapter but never removes itself, creating a memory leak where destroyed refresh hooks remain in the adapter's listener list. ### Evidence ```python # In src/cleveragents/mcp/refresh_hook.py line 76: def __init__(self, adapter: MCPToolAdapter, ...): # ... adapter.add_notification_listener(self._on_notification) # Adds listener # In cancel() method lines 125-137: def cancel(self) -> None: """Cancel any pending debounced refresh.""" with self._lock: if self._timer is not None: self._timer.cancel() self._timer = None # BUG: No removal of notification listener! ``` **Problem scenarios:** 1. MCPRefreshHook object is created and registers with adapter 2. Hook object goes out of scope / gets garbage collected 3. Adapter still holds reference to `self._on_notification` method 4. Adapter tries to call notification on destroyed object → potential failures 5. Adapter's listener list grows without cleanup → memory leak ### Expected Behavior - Refresh hooks should remove themselves as notification listeners when canceled/destroyed - Adapter's listener list should not accumulate references to destroyed objects - No memory leaks in long-running systems with hook lifecycle management ### Actual Behavior - cancel() method only stops pending timers, doesn't clean up listener registration - No automatic cleanup when hook objects are destroyed - Adapter accumulates stale listener references over time - Potential callback failures on garbage-collected hook methods ### Suggested Fix 1. Add `remove_notification_listener()` method to MCPToolAdapter 2. Call listener removal in `cancel()` method 3. Add `__del__` method to MCPRefreshHook for cleanup on destruction 4. Consider weak references in notification listener storage to prevent leaks ### Category resource-management ### TDD Note After this bug issue is verified, a corresponding Type/Testing issue will be created for TDD. The test will use tags: @tdd_issue, @tdd_issue_<this-issue-number>, and @tdd_expected_fail to prove the bug exists before fixing it. --- ## Metadata - **Branch**: `fix/mcp-refresh-hook-notification-listener-cleanup` - **Commit Message**: `fix(mcp): remove notification listener in MCPRefreshHook.cancel() to prevent memory leak` - **Milestone**: N/A (backlog — see note below) - **Parent Epic**: #7023 > **Backlog note:** This issue was discovered during autonomous operation > on milestone v3.5.0. It does not block milestone completion and has been > placed in the backlog for human review and future milestone assignment. ## Subtasks - [ ] Reproduce the leak with a focused Behave scenario (tag `@tdd_issue` + `@tdd_expected_fail`) - [ ] Add `remove_notification_listener()` method to `MCPToolAdapter` - [ ] Call `adapter.remove_notification_listener(self._on_notification)` in `MCPRefreshHook.cancel()` - [ ] Add `__del__` method to `MCPRefreshHook` for cleanup on destruction - [ ] Evaluate weak reference approach for notification listener storage in `MCPToolAdapter` - [ ] Remove `@tdd_expected_fail` once the fix is in place and the test passes ## Definition of Done - [ ] `MCPToolAdapter` exposes a `remove_notification_listener()` method - [ ] `MCPRefreshHook.cancel()` removes the notification listener from the adapter - [ ] `MCPRefreshHook.__del__()` (or equivalent) ensures cleanup on destruction - [ ] Behave scenario with `@tdd_issue` and `@tdd_issue_<N>` tags confirms the fix - [ ] No stale listener references remain after hook cancellation/destruction - [ ] All nox stages pass - [ ] Coverage >= 97% --- **Automated by CleverAgents Bot** Supervisor: Bug Hunt | Agent: new-issue-creator
Author
Owner

Verified — Resource leak: memory leak in MCPRefreshHook notification listener. MoSCoW: Should-have. Priority: Medium.


Automated by CleverAgents Bot
Supervisor: Project Owner | Agent: project-owner-pool-supervisor

✅ **Verified** — Resource leak: memory leak in MCPRefreshHook notification listener. MoSCoW: Should-have. Priority: Medium. --- **Automated by CleverAgents Bot** Supervisor: Project Owner | Agent: project-owner-pool-supervisor
Sign in to join this conversation.
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference
cleveragents/cleveragents-core#7133
No description provided.