How ego-browser Manages CDP Sessions and Automatic Reconnections

ego-browser maintains a single lightweight Chrome DevTools Protocol session per active tab with automatic reattachment, transparent retry logic, and a 2-second TTL-based refresh mechanism.

ego-browser runs inside the ego-lite browser runtime and communicates with Chrome DevTools Protocol (CDP) through the global ego.sendCDPMessage API. All session handling is centralized in browser-runtime.ts and consumed by high-level helpers like goto and switchTab. Understanding how ego-browser manages CDP sessions and reconnections is essential for building resilient browser automation agents.

CDP Session Lifecycle

Session Creation and Reuse

The ensureSession() function controls whether to reuse an existing session or create a new one. It checks the cached state.sessionId and compares its age against SESSION_TTL_MS = 2000 milliseconds.

// browser-runtime.ts:7-9
// Pseudocode showing TTL check logic
if (state.sessionId && (Date.now() - state.sessionAt) < SESSION_TTL_MS) {
  return state.sessionId;  // reuse existing session
}
// otherwise, attach to new target

If the session exceeds the TTL, the runtime sends Target.attachToTarget via rawCdp, stores the returned sessionId in state.sessionId, and records the target ID in state.sessionTargetId (browser-runtime.ts:27-35).

Page Event Enablement

After successful attachment, Page.enable is called once per session. A Set named pageEnabledSessions tracks which sessions already have page events enabled, preventing redundant calls (browser-runtime.ts:5-13).

Activity Tracking and Invalidation

Every successful attach updates state.sessionAt = Date.now(). When targets detach or are destroyed, the runtime clears the cached session:

// browser-runtime.ts:52-64
function handleTargetDetached(sessionId: string) {
  if (sessionId === state.sessionId) {
    invalidateSession();
    clearPendingDialogs();
  }
}

Automatic Reconnection Mechanism

The browserCdp() wrapper provides transparent session recovery. When a CDP call fails with a session-lost error matching the SESSION_LOST regex, and the call was not explicitly targeted at a specific session, the runtime:

  1. Calls invalidateSession() to clear stale state
  2. Invokes ensureSession() to establish a fresh session
  3. Retries the original CDP method with the new session

(browser-runtime.ts:94-103)

// browserCdp() wrapper with automatic retry
try {
  return await rawCdp(method, params);
} catch (err) {
  if (isSessionLost(err) && !params.sessionId) {
    invalidateSession();
    await ensureSession();
    return rawCdp(method, params);  // retry once
  }
  throw err;
}

TTL-Based Session Design

The deliberately short 2-second TTL ensures frequent session refresh. This design is safe because the underlying ego bridge can re-attach to the same target with negligible cost. Benefits include:

  • Rapid recovery from transient network hiccups
  • Quick adaptation to tab navigation and page reloads
  • Minimal stale state accumulation

Session State Storage

All session metadata lives in the singleton state object defined in state.ts:

// state.ts
export const state = {
  sessionId: null as string | null,        // active CDP session ID
  sessionTargetId: null as string | null,  // attached target ID
  sessionAt: 0,                            // timestamp of last attach
  sessionInflight: null as Promise<string> | null,  // deduplication lock
  preferredTargetId: null as string | null, // for tab switching
};

([state.ts](https://github.com/citrolabs/ego-lite/blob/main/package/ego-browser/src/state.ts))

Higher-Level Helper Integration

Navigation helpers delegate session management to the runtime layer:

  • goto calls browserCdp("Page.navigate", ...) which automatically resolves the active session
  • switchTab explicitly invalidates the old session and sets preferredTargetId so the next attach targets the newly activated tab (nav.ts:54-60)
// nav.ts:57-60 - switchTab invalidation pattern
await invalidateSession();
setPreferredTargetId(newTargetId);
// next ensureSession() will attach to preferredTargetId

Error Handling and Resilience

If ego.sendCDPMessage itself fails (for example, when the user's task becomes inactive), handleSendError rejects all pending requests with a unified EgoError. This prevents orphaned promises from infinitely blocking (browser-runtime.ts:22-30).

Practical Code Examples

Automatic Session Handling in Navigation

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

await goto("https://example.com");
// Session created or reused automatically; no manual management needed

Manual Session Retrieval (Rarely Required)

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

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

Forced Reconnection After Target Change

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

await switchTab("target-123");
// Invalidates old session, attaches to new target
// Subsequent calls use fresh session automatically

Low-Level CDP with Transparent Retry

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

const result = await browserCdp("Runtime.evaluate", { 
  expression: "document.title" 
});
console.log(result.result.value);
// Automatically re-attaches and retries if session is lost

Key Implementation Files

File Purpose
[src/browser-runtime.ts](https://github.com/citrolabs/ego-lite/blob/main/package/ego-browser/src/browser-runtime.ts) Core session management: browserCdp, ensureSession, invalidateSession, automatic reconnection
[src/driver/nav.ts](https://github.com/citrolabs/ego-lite/blob/main/package/ego-browser/src/driver/nav.ts) Navigation helpers that leverage the session layer
[src/state.ts](https://github.com/citrolabs/ego-lite/blob/main/package/ego-browser/src/state.ts) Shared mutable state for session identifiers and metadata

Summary

  • Single session per tab: ego-browser maintains one lightweight CDP session tied to the active target
  • 2-second TTL: Sessions refresh frequently to minimize stale state recovery time
  • Transparent retry: browserCdp() wrapper automatically re-attaches and retries on session loss
  • Centralized state: All session metadata in state.ts singleton with clear invalidation semantics
  • Helper delegation: High-level functions like goto and switchTab rely on the runtime layer rather than managing sessions directly

This architecture ensures browser automation agents continue operating through navigation, tab switches, and transient connection issues without explicit error handling in user code.

Frequently Asked Questions

What triggers a CDP session reconnection in ego-browser?

A reconnection triggers when browserCdp() detects a session-lost error (matching the SESSION_LOST regex) or when the session TTL expires. The runtime automatically invalidates the stale session, creates a new one via ensureSession(), and retries the failed call once.

Why is the session TTL only 2 seconds?

The short SESSION_TTL_MS = 2000 milliseconds ensures rapid recovery from state changes. Since the ego bridge re-attaches to targets with negligible overhead, frequent refresh prevents accumulation of stale session state while maintaining near-zero performance impact.

How does tab switching affect the CDP session?

switchTab explicitly calls invalidateSession() and sets preferredTargetId in state. The next CDP operation triggers ensureSession(), which attaches to the preferred target rather than the previous one, ensuring seamless continuation of automation on the new tab.

Can I manually control session lifecycle in ego-browser?

Direct session control is rarely needed. Use ensureSession() to force attachment, invalidateSession() to clear state, or setPreferredTarget() to influence the next attach target. Most workflows should rely on the automatic handling in browserCdp() and navigation helpers.

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 →