[Bug Hunt][Cycle 2][Reactive] Unsafe Event Loop Access in RouteBridge #7089

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

Metadata

  • Branch: bugfix/m3-reactive-route-bridge-unsafe-event-loop
  • Commit Message: fix(reactive): guard asyncio.get_event_loop() call in RouteBridge.check_downgrade_conditions()
  • Milestone: v3.2.0
  • Parent Epic: #7052

Background and Context

RouteBridge (src/cleveragents/reactive/route_bridge.py) contains an unsafe direct call to asyncio.get_event_loop().time() inside check_downgrade_conditions() at line 89. In Python 3.10+, asyncio.get_event_loop() emits a DeprecationWarning when there is no running event loop, and in Python 3.12+ it raises a RuntimeError outright when called from a thread or context that has no current event loop.

This method is part of the reactive routing layer and may be invoked from synchronous contexts (e.g., background threads, test harnesses, or non-async entry points) where no event loop is active, causing an immediate, unrecoverable crash.

Current Behavior

# Line 89: Direct event loop access without checking
if (
    last_updated
    and (asyncio.get_event_loop().time() - last_updated) > idle_threshold
):
    return True

asyncio.get_event_loop() is called unconditionally. If no event loop is running in the current thread, Python 3.12+ raises:

RuntimeError: There is no current event loop in thread '<thread-name>'.

Expected Behavior

The method must obtain the current time safely regardless of whether an event loop is running. The correct approach is to use asyncio.get_running_loop() when inside an async context, or fall back to time.monotonic() (or asyncio.get_event_loop_policy().get_event_loop() with a guard) when called from a synchronous context. The simplest safe fix is to replace asyncio.get_event_loop().time() with time.monotonic(), which is always available and returns the same monotonic clock used internally by asyncio.

Impact

  • RuntimeError crash in any non-async context that triggers check_downgrade_conditions()
  • Affects background threads, test runners, and any synchronous call path into the reactive routing layer
  • Immediate application failure — no graceful degradation

Acceptance Criteria

  • check_downgrade_conditions() no longer calls asyncio.get_event_loop() directly
  • Time comparison uses a safe, context-independent monotonic clock (e.g., time.monotonic())
  • No RuntimeError or DeprecationWarning is raised when the method is called from a non-async thread
  • BDD scenario added: check_downgrade_conditions() called from a synchronous (non-async) context returns the correct boolean without raising
  • All nox stages pass; coverage ≥ 97%

Subtasks

  • Replace asyncio.get_event_loop().time() with time.monotonic() (or equivalent safe call) at line 89 of src/cleveragents/reactive/route_bridge.py
  • Audit route_bridge.py for any other unsafe asyncio.get_event_loop() usages and fix them
  • Add import time if not already present
  • Tests (Behave): Add scenario for check_downgrade_conditions() invoked from a non-async context (tagged @tdd_issue @tdd_issue_<N>)
  • Verify coverage ≥ 97% via nox -s coverage_report
  • Run nox (all default sessions), fix any errors

Definition of Done

This issue is complete when:

  • All subtasks above are completed and checked off.
  • 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 Hunt | Agent: new-issue-creator

## Metadata - **Branch**: `bugfix/m3-reactive-route-bridge-unsafe-event-loop` - **Commit Message**: `fix(reactive): guard asyncio.get_event_loop() call in RouteBridge.check_downgrade_conditions()` - **Milestone**: v3.2.0 - **Parent Epic**: #7052 ## Background and Context `RouteBridge` (`src/cleveragents/reactive/route_bridge.py`) contains an unsafe direct call to `asyncio.get_event_loop().time()` inside `check_downgrade_conditions()` at line 89. In Python 3.10+, `asyncio.get_event_loop()` emits a `DeprecationWarning` when there is no running event loop, and in Python 3.12+ it raises a `RuntimeError` outright when called from a thread or context that has no current event loop. This method is part of the reactive routing layer and may be invoked from synchronous contexts (e.g., background threads, test harnesses, or non-async entry points) where no event loop is active, causing an immediate, unrecoverable crash. ## Current Behavior ```python # Line 89: Direct event loop access without checking if ( last_updated and (asyncio.get_event_loop().time() - last_updated) > idle_threshold ): return True ``` `asyncio.get_event_loop()` is called unconditionally. If no event loop is running in the current thread, Python 3.12+ raises: ``` RuntimeError: There is no current event loop in thread '<thread-name>'. ``` ## Expected Behavior The method must obtain the current time safely regardless of whether an event loop is running. The correct approach is to use `asyncio.get_running_loop()` when inside an async context, or fall back to `time.monotonic()` (or `asyncio.get_event_loop_policy().get_event_loop()` with a guard) when called from a synchronous context. The simplest safe fix is to replace `asyncio.get_event_loop().time()` with `time.monotonic()`, which is always available and returns the same monotonic clock used internally by asyncio. ## Impact - `RuntimeError` crash in any non-async context that triggers `check_downgrade_conditions()` - Affects background threads, test runners, and any synchronous call path into the reactive routing layer - Immediate application failure — no graceful degradation ## Acceptance Criteria - [ ] `check_downgrade_conditions()` no longer calls `asyncio.get_event_loop()` directly - [ ] Time comparison uses a safe, context-independent monotonic clock (e.g., `time.monotonic()`) - [ ] No `RuntimeError` or `DeprecationWarning` is raised when the method is called from a non-async thread - [ ] BDD scenario added: `check_downgrade_conditions()` called from a synchronous (non-async) context returns the correct boolean without raising - [ ] All nox stages pass; coverage ≥ 97% ## Subtasks - [ ] Replace `asyncio.get_event_loop().time()` with `time.monotonic()` (or equivalent safe call) at line 89 of `src/cleveragents/reactive/route_bridge.py` - [ ] Audit `route_bridge.py` for any other unsafe `asyncio.get_event_loop()` usages and fix them - [ ] Add `import time` if not already present - [ ] Tests (Behave): Add scenario for `check_downgrade_conditions()` invoked from a non-async context (tagged `@tdd_issue @tdd_issue_<N>`) - [ ] Verify coverage ≥ 97% via `nox -s coverage_report` - [ ] Run `nox` (all default sessions), fix any errors ## Definition of Done This issue is complete when: - All subtasks above are completed and checked off. - 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 Hunt | Agent: new-issue-creator
HAL9000 added this to the v3.2.0 milestone 2026-04-10 07:36:15 +00:00
Author
Owner

Verified — Critical concurrency bug: unsafe event loop access in RouteBridge. MoSCoW: Must-have. Priority: Critical.


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

✅ **Verified** — Critical concurrency bug: unsafe event loop access in RouteBridge. MoSCoW: Must-have. Priority: Critical. --- **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.

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