# How the CloddsBot Signal Bus Ensures Error Isolation

> Discover how the CloddsBot signal bus achieves error isolation by wrapping listeners in try-catch blocks, preventing listener exceptions from stopping signal delivery to other consumers.

- Repository: [AL/CloddsBot](https://github.com/alsk1992/CloddsBot)
- Tags: internals
- Published: 2026-09-13

---

**The CloddsBot signal bus overrides Node.js EventEmitter's default `emit` method to execute each listener inside isolated `try...catch` blocks, ensuring that exceptions in one consumer cannot propagate to stop signal delivery to others.**

The CloddsBot signal bus forms the backbone of this open-source trading bot's event-driven architecture, responsible for routing market signals to various trading engine components. By implementing defensive programming patterns on top of Node.js's native `EventEmitter`, the bus guarantees **error isolation**: a single faulty listener is logged and bypassed without disrupting the entire signal pipeline.

## Snapshotting Listeners to Prevent Side Effects

Before executing any handlers, the signal bus creates a defensive copy of the listener array. In [`src/gateway/signal-bus.ts`](https://github.com/alsk1992/CloddsBot/blob/main/src/gateway/signal-bus.ts), the overridden `emit` method calls `bus.rawListeners(event).slice()` to snapshot the current subscriber list.

This technique prevents side effects during iteration. If a listener adds or removes other subscribers while processing a signal, the isolation mechanism continues operating on the original snapshot, avoiding skipped handlers or concurrent modification errors.

## Wrapping Handlers in Try-Catch Blocks

The core error isolation logic resides in individually wrapping each listener execution. When the bus emits a signal, it iterates through the snapshot array and invokes each handler inside a dedicated `try...catch` block.

Any exception thrown by a consumer—whether a synchronous throw or an unhandled promise rejection—is caught locally and routed to the central logger. Crucially, the error does not propagate upward to halt the `emit` loop, ensuring that **all registered consumers receive the signal** regardless of individual failures.

## Preserving Once-Semantics Under Isolation

The implementation correctly handles `.once()` listeners, which EventEmitter wraps in intermediary functions. The signal bus identifies these wrappers through the `listener` property attached by `EventEmitter.once`.

Before invoking the underlying handler, the bus removes the wrapper from the internal listener list. This preserves the "once" semantics (the handler will not run again) while still subjecting the actual callback to the same `try...catch` isolation logic.

## Implementation Details in [`src/gateway/signal-bus.ts`](https://github.com/alsk1992/CloddsBot/blob/main/src/gateway/signal-bus.ts)

The key implementation lives in [`src/gateway/signal-bus.ts`](https://github.com/alsk1992/CloddsBot/blob/main/src/gateway/signal-bus.ts), where the `createSignalBus` factory function returns an extended `EventEmitter`. The custom `emit` method overrides the default behavior to implement the isolation logic described above.

According to the CloddsBot source code, this design allows the bus to serve as a reliable message broker between market data inputs and the trading engine, ensuring that a bug in one strategy handler cannot crash the entire application.

## Practical Usage Example

The following pattern demonstrates how consumers interact with the isolated bus:

```typescript
import { createSignalBus } from './gateway/signal-bus.js';

const bus = createSignalBus();

// Consumer A: Potentially unstable handler
bus.onSignal(async (signal) => {
  if (signal.strength < 0.5) throw new Error('weak signal');
  await processTrade(signal);
});

// Consumer B: Reliable logging handler
bus.onSignal((signal) => {
  console.log('Received signal for', signal.marketId);
});

// Emitting triggers both handlers; Consumer A's error is caught and logged,
// while Consumer B executes normally.
bus.emit('signal', { marketId: 'BTC-USD', strength: 0.3 });

```

In this scenario, the error thrown by Consumer A is caught internally, logged via the central logger, and the loop proceeds to execute Consumer B without interruption.

## Integration with the Signal Router

The [`src/signal-router/router.ts`](https://github.com/alsk1992/CloddsBot/blob/main/src/signal-router/router.ts) module relies heavily on this error isolation mechanism. As the component responsible for routing validated signals to the trading engine, it cannot afford to have one malformed signal or faulty strategy prevent other trades from executing.

By delegating all signal distribution to the bus configured in [`src/gateway/signal-bus.ts`](https://github.com/alsk1992/CloddsBot/blob/main/src/gateway/signal-bus.ts), the router ensures that individual strategy failures remain contained. The bus acts as a circuit breaker at the event layer, allowing the trading system to continue operating even when specific consumers encounter runtime errors.

## Verification Through Unit Testing

Error isolation is rigorously verified in [`tests/unit/signal-router.test.ts`](https://github.com/alsk1992/CloddsBot/blob/main/tests/unit/signal-router.test.ts). These tests specifically verify that the bus continues delivering signals even when listeners reject promises or throw synchronous exceptions.

The test suite confirms that the `emit` method returns successfully regardless of individual handler failures, and that the snapshotting mechanism prevents mid-iteration array mutations from affecting delivery guarantees.

## Summary

- **Snapshotting**: `rawListeners(event).slice()` creates a defensive copy of subscribers to prevent side effects during emission.
- **Try-Catch Isolation**: Each listener executes in an isolated block; errors are caught, logged, and suppressed to protect the event loop.
- **Once Handler Support**: The bus unwraps `.once()` listeners before invocation, preserving single-execution semantics while maintaining error isolation.
- **Continuous Delivery**: The emission loop proceeds through all listeners even after catching exceptions, ensuring 100% signal coverage across healthy consumers.

## Frequently Asked Questions

### What happens if a listener throws an error in the CloddsBot signal bus?

The error is caught by the custom `emit` implementation in [`src/gateway/signal-bus.ts`](https://github.com/alsk1992/CloddsBot/blob/main/src/gateway/signal-bus.ts), logged to the central logging system, and silently suppressed. The bus continues iterating through the remaining listeners, ensuring that one faulty consumer cannot block signal delivery to others.

### How does the bus handle listeners registered with `.once()`?

The overridden `emit` method detects EventEmitter's internal wrapper functions through the `listener` property. It removes the wrapper from the subscription list before invoking the underlying handler, ensuring the callback executes exactly once while still benefiting from the same `try...catch` error isolation as permanent listeners.

### Where is the error isolation logic implemented in the codebase?

The core logic resides in [`src/gateway/signal-bus.ts`](https://github.com/alsk1992/CloddsBot/blob/main/src/gateway/signal-bus.ts), specifically within the custom `emit` method that extends Node.js's native `EventEmitter`. The `createSignalBus` factory function returns the configured instance used throughout the application.

### Does error isolation affect performance or signal latency?

The performance impact is minimal and constant-time. The implementation adds only a shallow array copy via `.slice()` and basic exception handling overhead. For a trading bot handling high-frequency signals, this defensive overhead is negligible compared to the cost of allowing unhandled exceptions to crash the event pipeline.