# How ego-browser Manages CDP Session Transport and Timeouts: A Deep Dive into the Runtime Layer

> Discover how ego-browser manages CDP session transport and timeouts with its self-healing layer. Learn about automatic session attachment, response timeouts, and failed request retries for seamless CDP calls.

- Repository: [CitroLabs/ego-lite](https://github.com/citrolabs/ego-lite)
- Tags: deep-dive
- Published: 2026-08-29

---

**ego-browser handles Chrome DevTools Protocol (CDP) communication through a self-healing transport layer that automatically manages session attachment, enforces 15-second response timeouts, and retries failed requests when sessions are lost.** This architecture lets you call CDP methods without manually tracking session IDs or handling transient connection failures.

The **ego-lite** repository provides a lightweight browser automation toolkit where `ego-browser` wraps Chromium's raw CDP in a TypeScript runtime. The transport and timeout logic lives primarily in [`src/browser-runtime.ts`](https://github.com/citrolabs/ego-lite/blob/main/src/browser-runtime.ts), which exposes a single `browserCdp()` function that powers all higher-level APIs. Here's how it works under the hood.

## CDP Transport Layer: Message Routing with Promise Matching

Every CDP request flows through `rawCdp()` in [`browser-runtime.ts`](https://github.com/citrolabs/ego-lite/blob/main/browser-runtime.ts). The runtime converts your method call into a JSON payload containing four fields: an incrementing `message-id`, the CDP method name, optional parameters, and an optional `sessionId`. This payload passes to `globalThis.ego.sendCDPMessage`, the bridge injected by the ego host environment.

```typescript
// Conceptual flow inside rawCdp (browser-runtime.ts#L38-L70)
const id = ++messageIdCounter;
const payload = { id, method, params, sessionId };
globalThis.ego.sendCDPMessage(JSON.stringify(payload));

// Response handling via handleMessage
const pending = pendingPromises.get(id);
if (pending) {
  pending.resolve(result);
  clearTimeout(pending.timeout);
}

```

Incoming messages route to `handleMessage()`, which parses the JSON and matches responses to pending promises by `id`. The runtime rejects promises for CDP errors and resolves them with the `result` field on success.

## Session Management: Automatic Attachment with 2-Second TTL

For page-level CDP methods, **ego-browser eliminates manual session handling**. The runtime maintains a cached session ID with a 2-second time-to-live (`SESSION_TTL_MS = 2000`).

### How `ensureSession()` Works

```typescript
import { ensureSession, invalidateSession } from "ego-browser";

// Automatically creates or returns a fresh session
const sessionId = await ensureSession(); // browser-runtime.ts#L107-L136

```

The `ensureSession()` function implements this logic:

- **Cache check** – Returns the cached `state.sessionId` if `state.sessionTimestamp` is within the 2-second TTL
- **Target discovery** – Lists tabs via `Target.getTargets`, selects the active tab or preferred target
- **Session acquisition** – Calls `Target.attachToTarget` to obtain a new session ID
- **State update** – Stores the ID in `state.sessionId` with a fresh timestamp

### Session Invalidation

When the browser reports a detached target or other fatal conditions, `invalidateSession()` clears `state.sessionId` and `state.sessionTimestamp`. This forces `ensureSession()` to re-attach on the next call.

## Timeout Handling and Error Recovery

The runtime implements **two layers of failure protection** to keep CDP calls reliable.

### Response Timeouts (15 Seconds Default)

Each `rawCdp` call starts a `setTimeout` using `RESPONSE_TIMEOUT_MS` (15000 ms). If the timeout fires before a response arrives, the pending promise is removed and rejected:

```typescript
try {
  await browserCdp("Runtime.evaluate", { expression: "document.title" });
} catch (e) {
  // Error: "CDP request timed out: Runtime.evaluate"
}

```

Override per-call:

```typescript
await browserCdp("Network.enable", {}, undefined, 5000); // 5 second timeout

```

### Transport Failure Handling

If `sendCDPMessage` throws synchronously, the timeout clears immediately and the promise rejects with the thrown error. This prevents hanging promises when the bridge itself is unavailable.

### Automatic Retry on Session Loss

The runtime detects session loss through regex matching against `SESSION_LOST` patterns ("Session not found", "Target closed", etc.). When a raw request fails with a matching error—and the request wasn't explicitly targeting a specific session—the runtime:

1. Calls `invalidateSession()` to purge stale state
2. Invokes `ensureSession()` to obtain a fresh session
3. **Retries the request once** with the new session ID

This self-healing behavior covers transient detachments without surfacing errors to your code.

```typescript
// This call recovers automatically even if the session died mid-request
await browserCdp("Runtime.evaluate", { expression: "1+1" });
// Session lost → invalidated → re-attached → retried → returns 2

```

## Source File Reference

| File | Purpose |
|------|---------|
| [`src/browser-runtime.ts`](https://github.com/citrolabs/ego-lite/blob/main/src/browser-runtime.ts) | Core transport (`rawCdp`), session management (`ensureSession`, `invalidateSession`), timeout logic, event routing |
| [`src/state.ts`](https://github.com/citrolabs/ego-lite/blob/main/src/state.ts) | Mutable runtime state: `sessionId`, `sessionTimestamp`, configuration overrides |
| [`src/cdp-eval.ts`](https://github.com/citrolabs/ego-lite/blob/main/src/cdp-eval.ts) | Convenience wrappers (`cdp()`, `js()`) delegating to `browserCdp` |
| `src/driver/*.ts` | Higher-level APIs (pointer, keyboard, etc.) consuming `browserCdp` |

## Summary

- **Transport**: JSON payloads via `globalThis.ego.sendCDPMessage` with promise-based response matching in `handleMessage`
- **Sessions**: Automatic attachment with 2-second TTL caching in `ensureSession()`; explicit invalidation via `invalidateSession()`
- **Timeouts**: 15-second default per-request, configurable per call, cleared on response or synchronous failure
- **Recovery**: Single automatic retry when `SESSION_LOST` patterns match, triggered transparently in `rawCdp`

## Frequently Asked Questions

### How does ego-browser handle CDP session timeouts?

Each CDP request starts a `setTimeout` with `RESPONSE_TIMEOUT_MS` (15000 ms by default). If no response arrives before the timer fires, the pending promise rejects with `CDP request timed out: <method>`. You can override this per-call by passing a fourth argument to `browserCdp()`.

### What happens when a CDP session is lost mid-request?

The runtime detects session loss through regex matching against common error patterns. If detected and the request wasn't explicitly bound to a specific session, `invalidateSession()` clears stale state, `ensureSession()` creates a fresh session, and the request retries once automatically.

### Can I manually control the CDP session in ego-browser?

Yes, though it's rarely necessary. Import `ensureSession()` to force session creation or retrieval, or `invalidateSession()` to explicitly clear cached state. You can also pass a `sessionId` directly to `browserCdp()` to bypass automatic session management.

### Where is the CDP transport logic implemented in ego-lite?

The core implementation resides in [`package/ego-browser/src/browser-runtime.ts`](https://github.com/citrolabs/ego-lite/blob/main/package/ego-browser/src/browser-runtime.ts), specifically lines 38-70 for message routing and 107-136 for session management. Global state lives in [`src/state.ts`](https://github.com/citrolabs/ego-lite/blob/main/src/state.ts), and all public APIs ultimately delegate through this runtime layer.