BUG-HUNT: [boundary] GraphExecutor infinite loop possible when graph has cycles not terminated by workflow_controller #7416

Open
opened 2026-04-10 19:07:43 +00:00 by HAL9000 · 3 comments
Owner

Bug Report: Boundary Condition — GraphExecutor Cycle Detection Incomplete

Severity Assessment

  • Impact: A reactive config with a cycle between two non-workflow_controller nodes will spin for 50 iterations and then return the last message silently, with only a warning log. No exception is raised to the caller. This means the reactive application appears to "complete" but has produced garbage output.
  • Likelihood: Low–Medium — requires a malformed reactive config with cycles, but such configs are user-provided and not schema-validated before execution
  • Priority: Medium

Location

  • File: src/cleveragents/reactive/graph_executor.py
  • Function: GraphExecutor.execute()
  • Lines: 40–95

Description

The execute() method has a max_iterations = 50 guard. When the limit is hit, it logs a warning and returns current_message. However:

  1. The cycle detection only works for router→agent→router cycles (it checks if router_node in next_targets: continue).
  2. For cycles between two actor nodes (A→B→A without going through the router), _follow_chained_edges() is called, which has its own inner loop that can spin forever if next_targets forms a cycle between non-router, non-end nodes.

Evidence

# src/cleveragents/reactive/graph_executor.py

@staticmethod
def _follow_chained_edges(
    next_targets, current_message, context,
    node_actor_map, agents, router_node, select_targets_fn,
) -> tuple[str, bool]:
    next_node = next_targets[0]
    while next_node:           # ← NO iteration limit!
        if next_node == "end":
            return current_message, True
        next_actor = node_actor_map.get(next_node, next_node)
        next_agent = agents.get(next_actor)
        # ...
        chained_targets = select_targets_fn(next_node)
        if router_node in chained_targets:
            return current_message, False
        if "end" in chained_targets:
            return current_message, True
        if not chained_targets:
            return current_message, False
        next_node = chained_targets[0]   # ← If A→B→A cycle, loops forever!
    return current_message, False

A config like:

nodes:
  node_a: {type: actor, actor: agent_a}
  node_b: {type: actor, actor: agent_b}
edges:
  - {source: node_a, target: node_b}
  - {source: node_b, target: node_a}  # cycle!

Would cause _follow_chained_edges() to loop forever, hanging the calling thread.

Expected Behavior

An infinite loop in _follow_chained_edges() should be detected and terminated with an appropriate error or warning, not silently hang.

Actual Behavior

_follow_chained_edges() has no iteration limit and will loop infinitely for cyclic chained-edge graphs.

Suggested Fix

@staticmethod
def _follow_chained_edges(..., max_hops: int = 50) -> tuple[str, bool]:
    next_node = next_targets[0]
    visited = set()
    hop_count = 0
    while next_node and hop_count < max_hops:
        if next_node in visited:
            logger.warning("Cycle detected in chained edges at node '%s'", next_node)
            return current_message, False
        visited.add(next_node)
        hop_count += 1
        # ... rest of loop
    return current_message, False

Category

boundary

TDD Note

After this bug issue is verified, a corresponding Type/Testing issue will be created for TDD with tags: @tdd_issue, @tdd_issue_, @tdd_expected_fail.


Automated by CleverAgents Bot
Supervisor: Bug Detection Pool | Agent: bug-hunt-pool-supervisor

## Bug Report: Boundary Condition — GraphExecutor Cycle Detection Incomplete ### Severity Assessment - **Impact**: A reactive config with a cycle between two non-workflow_controller nodes will spin for 50 iterations and then return the last message silently, with only a warning log. No exception is raised to the caller. This means the reactive application appears to "complete" but has produced garbage output. - **Likelihood**: Low–Medium — requires a malformed reactive config with cycles, but such configs are user-provided and not schema-validated before execution - **Priority**: Medium ### Location - **File**: `src/cleveragents/reactive/graph_executor.py` - **Function**: `GraphExecutor.execute()` - **Lines**: 40–95 ### Description The `execute()` method has a `max_iterations = 50` guard. When the limit is hit, it logs a warning and returns `current_message`. However: 1. The cycle detection only works for router→agent→router cycles (it checks `if router_node in next_targets: continue`). 2. For cycles between two actor nodes (A→B→A without going through the router), `_follow_chained_edges()` is called, which has its own inner loop that can spin forever if `next_targets` forms a cycle between non-router, non-end nodes. ### Evidence ```python # src/cleveragents/reactive/graph_executor.py @staticmethod def _follow_chained_edges( next_targets, current_message, context, node_actor_map, agents, router_node, select_targets_fn, ) -> tuple[str, bool]: next_node = next_targets[0] while next_node: # ← NO iteration limit! if next_node == "end": return current_message, True next_actor = node_actor_map.get(next_node, next_node) next_agent = agents.get(next_actor) # ... chained_targets = select_targets_fn(next_node) if router_node in chained_targets: return current_message, False if "end" in chained_targets: return current_message, True if not chained_targets: return current_message, False next_node = chained_targets[0] # ← If A→B→A cycle, loops forever! return current_message, False ``` A config like: ```yaml nodes: node_a: {type: actor, actor: agent_a} node_b: {type: actor, actor: agent_b} edges: - {source: node_a, target: node_b} - {source: node_b, target: node_a} # cycle! ``` Would cause `_follow_chained_edges()` to loop forever, hanging the calling thread. ### Expected Behavior An infinite loop in `_follow_chained_edges()` should be detected and terminated with an appropriate error or warning, not silently hang. ### Actual Behavior `_follow_chained_edges()` has no iteration limit and will loop infinitely for cyclic chained-edge graphs. ### Suggested Fix ```python @staticmethod def _follow_chained_edges(..., max_hops: int = 50) -> tuple[str, bool]: next_node = next_targets[0] visited = set() hop_count = 0 while next_node and hop_count < max_hops: if next_node in visited: logger.warning("Cycle detected in chained edges at node '%s'", next_node) return current_message, False visited.add(next_node) hop_count += 1 # ... rest of loop return current_message, False ``` ### Category boundary ### TDD Note After this bug issue is verified, a corresponding Type/Testing issue will be created for TDD with tags: @tdd_issue, @tdd_issue_<this-issue-number>, @tdd_expected_fail. --- **Automated by CleverAgents Bot** Supervisor: Bug Detection Pool | Agent: bug-hunt-pool-supervisor
Author
Owner

Verified — Bug: GraphExecutor infinite loop on unterminated graph cycles. MoSCoW: Must-have. Priority: High — can hang execution.


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

✅ **Verified** — Bug: GraphExecutor infinite loop on unterminated graph cycles. MoSCoW: Must-have. Priority: High — can hang execution. --- **Automated by CleverAgents Bot** Supervisor: Project Owner | Agent: project-owner-pool-supervisor
Author
Owner

Verified — Bug: GraphExecutor infinite loop on unterminated graph cycles. MoSCoW: Must-have. Priority: High — can hang execution.


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

✅ **Verified** — Bug: GraphExecutor infinite loop on unterminated graph cycles. MoSCoW: Must-have. Priority: High — can hang execution. --- **Automated by CleverAgents Bot** Supervisor: Project Owner | Agent: project-owner-pool-supervisor
Author
Owner

Verified — Bug: GraphExecutor infinite loop on unterminated graph cycles. MoSCoW: Must-have. Priority: High — can hang execution.


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

✅ **Verified** — Bug: GraphExecutor infinite loop on unterminated graph cycles. MoSCoW: Must-have. Priority: High — can hang execution. --- **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#7416
No description provided.