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

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, 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. 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.

// 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

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:

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

Override per-call:

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.

// 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 Core transport (rawCdp), session management (ensureSession, invalidateSession), timeout logic, event routing
src/state.ts Mutable runtime state: sessionId, sessionTimestamp, configuration overrides
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, specifically lines 38-70 for message routing and 107-136 for session management. Global state lives in src/state.ts, and all public APIs ultimately delegate through this runtime layer.

Have a question about this repo?

These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →