BUG-HUNT: [concurrency] events/reactive.py emit passes same mutable event object to all handlers — handler mutation corrupts subsequent handlers #7497

Open
opened 2026-04-10 20:50:06 +00:00 by HAL9000 · 1 comment
Owner

Bug Report: Concurrency — ReactiveEventBus.emit Shares Mutable Event Across All Handlers

Severity Assessment

  • Impact: If any event handler mutates the event object, all subsequent handlers receive the modified (corrupted) version of the event, not the original
  • Likelihood: Medium — depends on whether handlers mutate events; Pydantic models are not frozen by default
  • Priority: High

Location

  • File: src/cleveragents/infrastructure/events/reactive.py
  • Function: emit
  • Lines: ~122–136
  • Category: data-integrity / concurrency

Description

The original mutable event object is passed to all registered handlers. The audit log correctly gets a deep copy, but all handlers share the same object reference. If handler A mutates any field on event (possible because Pydantic models are not frozen by default), handler B and all subsequent handlers receive the already-mutated version, not the original event.

Evidence

self._audit_log.append(event.model_copy(deep=True))   # audit gets a safe copy ✓

for handler in list(self._subscriptions.get(event.event_type, ())):
    try:
        handler(event)                                 # ← ALL handlers share the SAME mutable object

Scenario:

  1. emit(PlanCreatedEvent(plan_id="123", status="pending"))
  2. Handler A (telemetry) mutates event.status = "processing" for its needs
  3. Handler B (notification) receives event with status="processing" instead of "pending" — incorrect behavior

Expected Behavior

Each handler should receive an independent copy of the event, preventing mutation in one handler from affecting others.

Actual Behavior

All handlers share the same event object reference. Mutation in one handler corrupts the view for all subsequent handlers.

Suggested Fix

Either freeze DomainEvent (preferred, zero overhead) or copy per handler:

# Option A: freeze DomainEvent at the model level (preferred)
class DomainEvent(BaseModel):
    model_config = ConfigDict(frozen=True)

# Option B: copy per handler (heavier but safe without model changes)
for handler in list(self._subscriptions.get(event.event_type, ())):
    try:
        handler(event.model_copy(deep=True))

Category

data-integrity

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.


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

## Bug Report: Concurrency — `ReactiveEventBus.emit` Shares Mutable Event Across All Handlers ### Severity Assessment - **Impact**: If any event handler mutates the event object, all subsequent handlers receive the modified (corrupted) version of the event, not the original - **Likelihood**: Medium — depends on whether handlers mutate events; Pydantic models are not frozen by default - **Priority**: High ### Location - **File**: `src/cleveragents/infrastructure/events/reactive.py` - **Function**: `emit` - **Lines**: ~122–136 - **Category**: data-integrity / concurrency ### Description The original mutable event object is passed to all registered handlers. The audit log correctly gets a deep copy, but all handlers share the same object reference. If handler A mutates any field on `event` (possible because Pydantic models are not frozen by default), handler B and all subsequent handlers receive the already-mutated version, not the original event. ### Evidence ```python self._audit_log.append(event.model_copy(deep=True)) # audit gets a safe copy ✓ for handler in list(self._subscriptions.get(event.event_type, ())): try: handler(event) # ← ALL handlers share the SAME mutable object ``` **Scenario:** 1. `emit(PlanCreatedEvent(plan_id="123", status="pending"))` 2. Handler A (telemetry) mutates `event.status = "processing"` for its needs 3. Handler B (notification) receives event with `status="processing"` instead of `"pending"` — incorrect behavior ### Expected Behavior Each handler should receive an independent copy of the event, preventing mutation in one handler from affecting others. ### Actual Behavior All handlers share the same event object reference. Mutation in one handler corrupts the view for all subsequent handlers. ### Suggested Fix Either freeze `DomainEvent` (preferred, zero overhead) or copy per handler: ```python # Option A: freeze DomainEvent at the model level (preferred) class DomainEvent(BaseModel): model_config = ConfigDict(frozen=True) # Option B: copy per handler (heavier but safe without model changes) for handler in list(self._subscriptions.get(event.event_type, ())): try: handler(event.model_copy(deep=True)) ``` ### Category data-integrity ### 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. --- **Automated by CleverAgents Bot** Supervisor: Bug Detection Pool | Agent: bug-hunt-pool-supervisor
HAL9000 added this to the v3.5.0 milestone 2026-04-10 21:39:16 +00:00
Author
Owner

Issue triaged by project owner:

  • State: Verified
  • Priority: High — Concurrency/data integrity bug in autonomy hardening components that impacts M6 milestone functionality
  • Milestone: v3.5.0 (M6: Autonomy Hardening) — This component is core to autonomous execution, guardrails, and context management
  • Story Points: 3 (M) — Bug fix with clear reproduction path
  • MoSCoW: Must Have — Autonomy hardening requires correct concurrency and data integrity
  • Type: Bug

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

Issue triaged by project owner: - **State**: Verified - **Priority**: High — Concurrency/data integrity bug in autonomy hardening components that impacts M6 milestone functionality - **Milestone**: v3.5.0 (M6: Autonomy Hardening) — This component is core to autonomous execution, guardrails, and context management - **Story Points**: 3 (M) — Bug fix with clear reproduction path - **MoSCoW**: Must Have — Autonomy hardening requires correct concurrency and data integrity - **Type**: Bug --- **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#7497
No description provided.