BUG-HUNT: [concurrency] AutoDebugAgent.invoke/ainvoke/stream default thread_id="auto-debug" causes state contamination across independent debugging sessions #6535

Open
opened 2026-04-09 21:15:52 +00:00 by HAL9000 · 0 comments
Owner

Bug Report: [concurrency] — AutoDebugAgent shared default thread_id causes checkpoint state bleed

Severity Assessment

  • Impact: When multiple independent debugging sessions invoke AutoDebugAgent without providing an explicit config, all sessions share the same checkpoint thread "auto-debug". The MemorySaver checkpointer merges and replays state from prior sessions into new ones, causing new debug sessions to inherit messages, attempted_fixes, and current_fix from previous sessions. This corrupts debugging results.
  • Likelihood: High — callers who rely on the documented default-invocation pattern (no config) will always be affected
  • Priority: High

Location

  • File: src/cleveragents/agents/graphs/auto_debug.py
  • Class: AutoDebugAgent
  • Methods: invoke, ainvoke, stream
  • Lines: 183, 196, 213

Description

All three public invocation methods default to the same hardcoded thread_id:

# auto_debug.py lines 183, 196, 213
def invoke(self, input_state, config=None) -> AutoDebugState:
    config = config or {"configurable": {"thread_id": "auto-debug"}}  # HARDCODED

async def ainvoke(self, input_state, config=None) -> AutoDebugState:
    config = config or {"configurable": {"thread_id": "auto-debug"}}  # HARDCODED

def stream(self, input_state, config=None) -> Iterator[dict[str, Any]]:
    config = config or {"configurable": {"thread_id": "auto-debug"}}  # HARDCODED

MemorySaver (the graph's checkpointer) uses thread_id as the isolation key. When two debugging sessions both use thread_id="auto-debug", the checkpointer stores their state in the same bucket. On the second invocation, the graph resumes from where the previous invocation left off, inheriting stale messages, attempted_fixes, and current_fix fields.

The __init__.py module docstring explicitly warns: "Always provide a thread_id for checkpoint isolation", but the implementation provides a shared fallback that violates this principle.

Expected Behavior

The default thread_id should be unique per invocation, e.g., using uuid4():

from uuid import uuid4

config = config or {"configurable": {"thread_id": f"auto-debug-{uuid4()}"}}

Actual Behavior

All invocations without an explicit config share a single checkpoint, causing state contamination between independent debugging sessions.

Category

concurrency / state-management

TDD Note

After this bug is verified, a Type/Testing issue will be created with a TDD test tagged @tdd_issue, @tdd_issue_<this-issue-number>, and @tdd_expected_fail to prove the bug exists before fixing it.


Automated by CleverAgents Bot
Supervisor: Bug Hunting | Agent: bug-hunter

## Bug Report: [concurrency] — `AutoDebugAgent` shared default `thread_id` causes checkpoint state bleed ### Severity Assessment - **Impact**: When multiple independent debugging sessions invoke `AutoDebugAgent` without providing an explicit `config`, all sessions share the same checkpoint thread `"auto-debug"`. The `MemorySaver` checkpointer merges and replays state from prior sessions into new ones, causing new debug sessions to inherit `messages`, `attempted_fixes`, and `current_fix` from previous sessions. This corrupts debugging results. - **Likelihood**: High — callers who rely on the documented default-invocation pattern (no config) will always be affected - **Priority**: High ### Location - **File**: `src/cleveragents/agents/graphs/auto_debug.py` - **Class**: `AutoDebugAgent` - **Methods**: `invoke`, `ainvoke`, `stream` - **Lines**: 183, 196, 213 ### Description All three public invocation methods default to the same hardcoded `thread_id`: ```python # auto_debug.py lines 183, 196, 213 def invoke(self, input_state, config=None) -> AutoDebugState: config = config or {"configurable": {"thread_id": "auto-debug"}} # HARDCODED async def ainvoke(self, input_state, config=None) -> AutoDebugState: config = config or {"configurable": {"thread_id": "auto-debug"}} # HARDCODED def stream(self, input_state, config=None) -> Iterator[dict[str, Any]]: config = config or {"configurable": {"thread_id": "auto-debug"}} # HARDCODED ``` `MemorySaver` (the graph's checkpointer) uses `thread_id` as the isolation key. When two debugging sessions both use `thread_id="auto-debug"`, the checkpointer stores their state in the same bucket. On the second invocation, the graph resumes from where the previous invocation left off, inheriting stale `messages`, `attempted_fixes`, and `current_fix` fields. The `__init__.py` module docstring explicitly warns: _"Always provide a `thread_id` for checkpoint isolation"_, but the implementation provides a shared fallback that violates this principle. ### Expected Behavior The default `thread_id` should be unique per invocation, e.g., using `uuid4()`: ```python from uuid import uuid4 config = config or {"configurable": {"thread_id": f"auto-debug-{uuid4()}"}} ``` ### Actual Behavior All invocations without an explicit config share a single checkpoint, causing state contamination between independent debugging sessions. ### Category concurrency / state-management ### TDD Note After this bug is verified, a Type/Testing issue will be created with a TDD test tagged `@tdd_issue`, `@tdd_issue_<this-issue-number>`, and `@tdd_expected_fail` to prove the bug exists before fixing it. --- **Automated by CleverAgents Bot** Supervisor: Bug Hunting | Agent: bug-hunter
HAL9000 added this to the v3.2.0 milestone 2026-04-09 21:27:52 +00:00
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#6535
No description provided.