How ego-browser Manages CDP Session Management and Automatic Reconnection

ego-browser centralizes Chrome DevTools Protocol (CDP) session handling in browser-runtime.ts, using a 2000ms TTL cache and transparent retry logic to automatically re-attach to targets when sessions expire or disconnect.

The ego-lite browser runtime provides automation capabilities through ego-browser, a TypeScript layer that communicates with Chrome via the Chrome DevTools Protocol (CDP). Unlike raw CDP clients that require manual session handling, ego-browser implements a robust state machine in package/ego-browser/src/browser-runtime.ts that abstracts attachment logic, TTL-based reuse, and automatic reconnection behind a simple API.

Session Lifecycle Management

All CDP session operations are centralized in package/ego-browser/src/browser-runtime.ts. The runtime maintains a single active session per tab through a strict lifecycle protocol.

Session Creation and Target Attachment

The ensureSession() function checks state.sessionId to determine if a valid session exists. If the cached session is younger than SESSION_TTL_MS (2000ms), it returns the existing identifier. Otherwise, the runtime sends Target.attachToTarget via the low-level rawCdp method to create a new session attachment. Upon successful attachment, the returned sessionId is stored in state.sessionId and the target identifier in state.sessionTargetId (lines 27-35).

Event Enablement and Activity Tracking

After attaching to a target, the runtime calls Page.enable once per session to receive page-level events such as dialogs and screencasts. A pageEnabledSessions Set tracks which sessions have already been initialized to prevent duplicate enablement calls (lines 5-13). The runtime records the attachment timestamp in state.sessionAt using Date.now(), which drives the TTL validation logic in subsequent calls (lines 36-38).

Session Invalidation

When a target detaches or is destroyed, the runtime listens for Target.detachedFromTarget or Target.targetDestroyed events. These trigger invalidateSession(), which clears state.sessionId, state.sessionTargetId, and any pending dialogs (lines 52-64).

Automatic Reconnection Mechanism

The browserCdp() function wraps every CDP request to provide transparent fault tolerance. When a call fails with a session-lost error matching the SESSION_LOST regex pattern, the runtime checks if the call was explicitly targeted at a specific session. If not, it automatically calls invalidateSession() to clear stale state, invokes ensureSession() to establish a fresh attachment, and retries the original CDP method (lines 94-103).

This mechanism ensures that agents continue functioning through page navigations, tab switches, or transient network interruptions without manual intervention.

TTL-Based Session Reuse Strategy

The SESSION_TTL_MS constant is set to 2000 milliseconds, a deliberately short window. This design choice ensures frequent session refreshment, which is safe because the underlying ego bridge can re-attach to targets with negligible overhead. The short TTL guarantees that transient network hiccups or tab navigations quickly trigger fresh attachments rather than attempting to reuse stale connections that may fail mid-operation.

High-level navigation functions delegate session management to the runtime layer rather than handling connections directly.

Page Navigation: The goto() function in package/ego-browser/src/driver/nav.ts calls browserCdp("Page.navigate", …), which internally resolves the active session through ensureSession() before executing the navigation command.

Tab Switching: The switchTab() function explicitly calls invalidateSession() to clear the current session cache and sets state.preferredTargetId to the new target identifier (lines 54-60). This forces the next CDP call to attach to the newly activated tab rather than attempting to reuse the previous tab's session.

State Storage Architecture

All session-related state lives in package/ego-browser/src/state.ts within a shared mutable singleton:

export const state = {
  sessionId: null as string | null,
  sessionTargetId: null as string | null,
  sessionAt: 0,
  sessionInflight: null as Promise<string> | null,
  preferredTargetId: null as string | null,
};

The sessionInflight field prevents race conditions by tracking in-progress attachment promises, while preferredTargetId enables tab switching workflows by specifying which target should receive the next attachment.

Practical Implementation Examples

Basic navigation with automatic session handling:

import { goto } from "./driver/nav.js";

await goto("https://example.com");
// Session is created or reused automatically

Manual session retrieval for debugging:

import { ensureSession } from "./browser-runtime.js";

const sessionId = await ensureSession();
console.log("Current CDP session:", sessionId);

Force reconnection after target changes:

import { invalidateSession, setPreferredTarget } from "./browser-runtime.js";
import { switchTab } from "./driver/nav.js";

await switchTab("target-123");
// Old session invalidated, new session attaches automatically

Low-level CDP calls with automatic retry:

import { browserCdp } from "./browser-runtime.js";

const result = await browserCdp("Runtime.evaluate", { expression: "1+1" });
console.log(result.result.value); // → 2
// Automatically retries if session was lost

Error handling for inactive tasks occurs when ego.sendCDPMessage itself fails (e.g., when the user task becomes inactive). The handleSendError function in browser-runtime.ts (lines 22-30) rejects all pending requests with a unified EgoError, preventing orphaned promises from timing out indefinitely.

Summary

  • ego-browser centralizes CDP session management in browser-runtime.ts, providing a single source of truth for attachment state.
  • Automatic reconnection occurs through the browserCdp() wrapper, which detects session-lost errors and transparently retries after re-attaching.
  • TTL-based caching uses a 2000ms window to balance performance with freshness, ensuring quick recovery from navigation or network issues.
  • Navigation helpers like goto and switchTab rely on the session layer, with switchTab explicitly invalidating old sessions when changing targets.
  • State management in state.ts tracks session identifiers, timestamps, and pending operations to prevent race conditions.

Frequently Asked Questions

What triggers an automatic reconnection in ego-browser?

When a CDP request fails with a session-lost error matching the SESSION_LOST regex, the browserCdp() wrapper automatically invalidates the stale session, creates a new attachment via ensureSession(), and retries the original request. This occurs transparently in browser-runtime.ts without requiring explicit reconnection logic in user code.

How long does ego-browser cache CDP sessions?

The runtime caches sessions for exactly SESSION_TTL_MS (2000 milliseconds) as defined in browser-runtime.ts. After this TTL expires, ensureSession() creates a fresh attachment even if the previous session might still be valid, ensuring rapid recovery from transient connection issues.

Why does switchTab explicitly invalidate the session while goto does not?

switchTab changes the active target to a different tab, making the previous session ID invalid for the new context. It calls invalidateSession() to clear cached state and sets preferredTargetId to force attachment to the new target. In contrast, goto navigates within the same tab, allowing the existing session to remain valid for subsequent operations.

Where is the session state stored in ego-browser?

All session identifiers, timestamps, and configuration flags reside in the singleton state object exported from package/ego-browser/src/state.ts, including sessionId, sessionTargetId, sessionAt, and sessionInflight for tracking concurrent attachment operations.

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 →