# How Event Path Tracking Works Across Multiple Bubus Buses

> Understand how Bubus event path tracking automatically records eventBus history preventing infinite loops in your browserUse/bubus repository for hierarchical routing.

- Repository: [Browser Use/bubus](https://github.com/browser-use/bubus)
- Tags: internals
- Published: 2026-02-26

---

**Bubus automatically records every `EventBus` an event travels through in the `event_path` list, enabling hierarchical routing and preventing infinite forwarding loops across interconnected buses.**

The `browser-use/bubus` library implements a robust mechanism for tracking event propagation across multiple EventBus instances. By maintaining an `event_path` field on every event model, bubus creates an audit trail that shows exactly which buses have processed an event. This event path tracking is essential for building complex hierarchical event-driven architectures while avoiding circular dependencies.

## The event_path Field in BaseEvent

In [`bubus/models.py`](https://github.com/browser-use/bubus/blob/main/bubus/models.py), the `BaseEvent` model defines the path tracking infrastructure at line 237:

```python
event_path: list[PythonIdentifierStr] = Field(
    default_factory=list,
    description='Path tracking for event routing'
)

```

This field starts as an empty list for new events and accumulates bus names as the event propagates. The model also provides an enhanced `__str__` representation (lines 71-76) that displays the path for debugging purposes, excluding the origin function name to focus on bus traversal.

## Updating the Path During Dispatch

When an event moves between buses, the `EventBus.dispatch` method in [`bubus/service.py`](https://github.com/browser-use/bubus/blob/main/bubus/service.py) (lines 530-540) updates the tracking information:

```python
if self.name not in event.event_path:
    event.event_path.append(self.name)
else:
    logger.debug(
        f'⚠️ {self}.dispatch({event.event_type}) - Bus already in path, '
        f'not adding again. Path: {event.event_path}'
    )

```

**First-time visits** append the current bus name to the list. **Repeated visits** are ignored to prevent duplicate entries. The same `BaseEvent` instance travels through the entire chain, so the path accumulates cumulatively without object copying.

## Loop Prevention via Path Inspection

To stop events from circulating infinitely, bubus checks the path before invoking forwarding handlers. The `_would_create_loop` logic in [`bubus/service.py`](https://github.com/browser-use/bubus/blob/main/bubus/service.py) (lines 1240-1248) implements this safety mechanism:

```python
if hasattr(handler, '__self__') and isinstance(handler.__self__, EventBus) \
        and handler.__name__ == 'dispatch':
    target_bus = handler.__self__
    if target_bus.name in event.event_path:
        logger.debug(
            f'⚠️ {self} handler {get_handler_name(handler)} '
            f'({event}) skipped to prevent infinite forwarding loop '
            f'with {target_bus.name}'
        )
        return True   # skip the handler

```

If the target bus name already exists in `event.event_path`, the handler is skipped and a debug warning is emitted. This check runs automatically when one bus forwards events to another via handler registration.

## Practical Examples

### Simple Three-Level Hierarchy

Consider a parent-child bus relationship as tested in [`tests/test_eventbus.py`](https://github.com/browser-use/bubus/blob/main/tests/test_eventbus.py):

```python
parent_bus = EventBus(name='ParentBus')
child_bus = EventBus(name='ChildBus')
subchild_bus = EventBus(name='SubchildBus')

# Forwarding chain

child_bus.on('*', parent_bus.dispatch)
subchild_bus.on('*', child_bus.dispatch)

event = UserActionEvent(action='bubble_test', user_id='test')
subchild_bus.dispatch(event)

# Results in event.event_path == ['SubchildBus', 'ChildBus', 'ParentBus']

```

The event originates at `SubchildBus`, travels through `ChildBus`, and finally reaches `ParentBus`, with each step recorded in the path (see line 45 of the test file).

### Circular Subscription Prevention

When buses forward to each other in a cycle, the path prevents infinite loops (see [`tests/test_eventbus.py`](https://github.com/browser-use/bubus/blob/main/tests/test_eventbus.py), lines 26-30):

```python
peer1 = EventBus(name='Peer1')
peer2 = EventBus(name='Peer2')
peer3 = EventBus(name='Peer3')

# Create a loop: Peer1 -> Peer2 -> Peer3 -> Peer1

peer1.on('*', peer2.dispatch)
peer2.on('*', peer3.dispatch)
peer3.on('*', peer1.dispatch)

```

When `peer1` dispatches an event, the path becomes `['Peer1', 'Peer2', 'Peer3']`. When `peer3` attempts to forward back to `peer1`, `_would_create_loop` detects that `'Peer1'` already exists in the path and halts the propagation, preventing the infinite cycle.

## Accessing the Path in Application Code

User handlers can inspect the event history through the public `event_path` attribute:

```python
async def my_handler(event: BaseEvent):
    print(event.event_path)  # ['Subbus', 'ParentBus']

    print(event)             # String representation includes path

```

Because the path is a standard Python list, you can check the origin, count hops, or make routing decisions based on previous processing steps.

## Summary

- **`event_path`** is a mutable list defined on `BaseEvent` in [`bubus/models.py`](https://github.com/browser-use/bubus/blob/main/bubus/models.py) (line 237) that tracks bus traversal history.
- **Dispatch augmentation** in [`bubus/service.py`](https://github.com/browser-use/bubus/blob/main/bubus/service.py) (lines 530-540) appends bus names cumulatively as events propagate.
- **Loop prevention** occurs via `_would_create_loop` (lines 1240-1248), which skips forwarding handlers when the target bus already appears in the path.
- **Hierarchical routing** becomes traceable and debuggable through the path string representation and logging integration.
- **Circular dependencies** are automatically detected and blocked without explicit user intervention.

## Frequently Asked Questions

### How does bubus prevent infinite loops when buses forward to each other?

Bubus checks `event.event_path` before executing forwarding handlers. If the target bus name already exists in the path list, the dispatch is skipped and a debug warning is logged. This automatic check in `_would_create_loop` (lines 1240-1248 of [`bubus/service.py`](https://github.com/browser-use/bubus/blob/main/bubus/service.py)) ensures cyclic bus relationships don't cause stack overflow or infinite processing.

### Can I modify the event_path manually in my event handlers?

Yes, because `event_path` is a standard Python list attached to the event instance, you can inspect or modify it in your handlers. However, manual modifications are generally unnecessary since `EventBus.dispatch` automatically appends bus names, and the loop prevention logic depends on accurate path data.

### What information does the event_path actually store?

The path stores **bus names** as strings (validated as `PythonIdentifierStr`), not bus objects or handler references. Each time an event enters a new bus via `dispatch`, that bus's name appends to the list if not already present, creating a chronological record of the event's journey through your bus hierarchy.

### Does event path tracking impact performance?

The overhead is minimal because Bubus mutates the existing list in-place rather than copying event objects. The check for existing bus names uses simple list containment testing, and the loop prevention check only runs when forwarding between buses via handler registration, making it efficient for most use cases.