BUG-HUNT: [resource] Potential resource leak in Agent class due to manual disposal #1799

Open
opened 2026-04-02 23:52:17 +00:00 by freemo · 1 comment
Owner

Metadata

  • Branch: fix/resource-agent-context-manager-disposal
  • Commit Message: fix(agents): implement async context manager on Agent to prevent resource leaks
  • Milestone: v3.7.0
  • Parent Epic: #362

Bug Report: [resource] — Potential resource leak in Agent class due to manual disposal

Severity Assessment

  • Impact: If the Agent's dispose method is not called, it could lead to resource leaks, especially if the underlying rx streams are connected to network resources or other external entities that need to be explicitly closed.
  • Likelihood: High. It is easy for a developer using the Agent class to forget to call the dispose method, as there is no enforcement mechanism.
  • Priority: Medium

Location

  • File: src/cleveragents/agents/base.py
  • Function/Class: Agent
  • Lines: 100-104

Description

The Agent class in base.py provides a dispose method to clean up its input_stream and output_stream. However, the responsibility of calling this method is left to the consumer of the agent. There is no automatic mechanism, such as a context manager, to ensure that dispose is called when the agent is no longer in use. This can lead to resource leaks, especially in long-running applications where many agents might be created and destroyed.

Evidence

    def dispose(self) -> None:
        if hasattr(self.input_stream, "dispose"):
            self.input_stream.dispose()
        if hasattr(self.output_stream, "dispose"):
            self.output_stream.dispose()

Expected Behavior

The Agent class should provide a mechanism for automatic resource cleanup to prevent potential leaks. Implementing the agent as an async context manager would be an idiomatic way to solve this in Python.

Actual Behavior

Resource cleanup is manual, relying on the consumer to explicitly call the dispose method.

Suggested Fix

Implement the Agent class as an async context manager by adding __aenter__ and __aexit__ methods. The __aexit__ method should call the dispose method.

Example:

class Agent(ABC):
    # ... existing methods ...

    async def __aenter__(self) -> "Agent":
        return self

    async def __aexit__(self, exc_type, exc_val, exc_tb) -> None:
        self.dispose()

    def dispose(self) -> None:
        if hasattr(self.input_stream, "dispose"):
            self.input_stream.dispose()
        if hasattr(self.output_stream, "dispose"):
            self.output_stream.dispose()

This would allow developers to use the agent in an async with block, ensuring that resources are always cleaned up correctly.

Category

resource

Subtasks

  • Add __aenter__ method to Agent class returning self with full type annotation
  • Add __aexit__ method to Agent class that calls self.dispose() with correct signature (exc_type, exc_val, exc_tb) and return type annotation
  • Audit all subclasses of Agent to ensure they do not override dispose in a way that breaks the context manager contract
  • Update any existing usages of Agent in the codebase to prefer async with where appropriate
  • Write Behave scenario: Agent used as async context manager calls dispose on exit even when an exception is raised
  • Write Behave scenario: Agent used as async context manager calls dispose on normal exit
  • Write Behave scenario: input_stream and output_stream are properly disposed when context manager exits
  • Run nox -s typecheck — verify Pyright passes with no # type: ignore suppressions
  • Run nox -s unit_tests — verify all Behave scenarios pass
  • Run nox -s coverage_report — verify coverage ≥ 97%
  • Run nox (all default sessions), fix any errors

Definition of Done

  • All subtasks above are completed and checked off.
  • Agent class is usable as an async context manager and guarantees dispose is called on exit.
  • No # type: ignore or type-suppression comments introduced.
  • A Git commit is created where the first line of the commit message matches the Commit Message in Metadata exactly, followed by a blank line, then additional lines providing relevant details about the implementation.
  • The commit is pushed to the remote on the branch matching the Branch in Metadata exactly.
  • The commit is submitted as a pull request to master, reviewed, and merged before this issue is marked done.
  • All nox stages pass.
  • Coverage >= 97%.

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

## Metadata - **Branch**: `fix/resource-agent-context-manager-disposal` - **Commit Message**: `fix(agents): implement async context manager on Agent to prevent resource leaks` - **Milestone**: v3.7.0 - **Parent Epic**: #362 ## Bug Report: [resource] — Potential resource leak in Agent class due to manual disposal ### Severity Assessment - **Impact**: If the `Agent`'s `dispose` method is not called, it could lead to resource leaks, especially if the underlying `rx` streams are connected to network resources or other external entities that need to be explicitly closed. - **Likelihood**: High. It is easy for a developer using the `Agent` class to forget to call the `dispose` method, as there is no enforcement mechanism. - **Priority**: Medium ### Location - **File**: `src/cleveragents/agents/base.py` - **Function/Class**: `Agent` - **Lines**: 100-104 ### Description The `Agent` class in `base.py` provides a `dispose` method to clean up its `input_stream` and `output_stream`. However, the responsibility of calling this method is left to the consumer of the agent. There is no automatic mechanism, such as a context manager, to ensure that `dispose` is called when the agent is no longer in use. This can lead to resource leaks, especially in long-running applications where many agents might be created and destroyed. ### Evidence ```python def dispose(self) -> None: if hasattr(self.input_stream, "dispose"): self.input_stream.dispose() if hasattr(self.output_stream, "dispose"): self.output_stream.dispose() ``` ### Expected Behavior The `Agent` class should provide a mechanism for automatic resource cleanup to prevent potential leaks. Implementing the agent as an async context manager would be an idiomatic way to solve this in Python. ### Actual Behavior Resource cleanup is manual, relying on the consumer to explicitly call the `dispose` method. ### Suggested Fix Implement the `Agent` class as an async context manager by adding `__aenter__` and `__aexit__` methods. The `__aexit__` method should call the `dispose` method. Example: ```python class Agent(ABC): # ... existing methods ... async def __aenter__(self) -> "Agent": return self async def __aexit__(self, exc_type, exc_val, exc_tb) -> None: self.dispose() def dispose(self) -> None: if hasattr(self.input_stream, "dispose"): self.input_stream.dispose() if hasattr(self.output_stream, "dispose"): self.output_stream.dispose() ``` This would allow developers to use the agent in an `async with` block, ensuring that resources are always cleaned up correctly. ### Category resource ## Subtasks - [ ] Add `__aenter__` method to `Agent` class returning `self` with full type annotation - [ ] Add `__aexit__` method to `Agent` class that calls `self.dispose()` with correct signature (`exc_type`, `exc_val`, `exc_tb`) and return type annotation - [ ] Audit all subclasses of `Agent` to ensure they do not override `dispose` in a way that breaks the context manager contract - [ ] Update any existing usages of `Agent` in the codebase to prefer `async with` where appropriate - [ ] Write Behave scenario: `Agent` used as async context manager calls `dispose` on exit even when an exception is raised - [ ] Write Behave scenario: `Agent` used as async context manager calls `dispose` on normal exit - [ ] Write Behave scenario: `input_stream` and `output_stream` are properly disposed when context manager exits - [ ] Run `nox -s typecheck` — verify Pyright passes with no `# type: ignore` suppressions - [ ] Run `nox -s unit_tests` — verify all Behave scenarios pass - [ ] Run `nox -s coverage_report` — verify coverage ≥ 97% - [ ] Run `nox` (all default sessions), fix any errors ## Definition of Done - [ ] All subtasks above are completed and checked off. - [ ] `Agent` class is usable as an async context manager and guarantees `dispose` is called on exit. - [ ] No `# type: ignore` or type-suppression comments introduced. - [ ] A Git commit is created where the **first line** of the commit message matches the Commit Message in Metadata exactly, followed by a blank line, then additional lines providing relevant details about the implementation. - [ ] The commit is pushed to the remote on the branch matching the **Branch** in Metadata exactly. - [ ] The commit is submitted as a **pull request** to `master`, reviewed, and **merged** before this issue is marked done. - [ ] All nox stages pass. - [ ] Coverage >= 97%. --- **Automated by CleverAgents Bot** Supervisor: Bug Hunting | Agent: ca-new-issue-creator
freemo added this to the v3.7.0 milestone 2026-04-02 23:54:01 +00:00
Author
Owner

Issue triaged by project owner:

  • State: Verified
  • MoSCoW: MoSCoW/Should Have — bug or error handling improvement.

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

Issue triaged by project owner: - **State**: Verified - **MoSCoW**: MoSCoW/Should Have — bug or error handling improvement. --- **Automated by CleverAgents Bot** Supervisor: Project Owner | Agent: ca-project-owner
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.

Blocks
#362 Epic: Security & Safety Hardening
cleveragents/cleveragents-core
Reference
cleveragents/cleveragents-core#1799
No description provided.