# How ego‑browser Handles CDP Session‑Lost Errors and Re‑Attaches Sessions

> ego-browser automatically handles CDP session-lost errors, invalidating stale sessions and re-attaching to fresh targets for seamless recovery. Learn how.

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

---

**ego‑browser automatically detects Chrome DevTools Protocol (CDP) session‑lost errors, invalidates stale sessions, and retries requests after re‑attaching to a fresh target—making session recovery transparent to callers.**

The [ego‑browser](https://github.com/citrolabs/ego-lite) runtime (part of the `citrolabs/ego-lite` repository) implements a resilient CDP transport layer that shields agent scripts from transient browser disconnections. This article explains how the session‑loss detection and automatic re‑attachment mechanism works in practice, with reference to the actual source implementation.

## Detecting Session‑Lost Errors with Pattern Matching

Session loss manifests through several distinct error messages from the Chrome DevTools Protocol. In [`src/browser-runtime.ts`](https://github.com/citrolabs/ego-lite/blob/main/src/browser-runtime.ts), ego‑browser defines a **regular expression constant** that captures all common variants:

```typescript
// browser-runtime.ts lines 9-10
const SESSION_LOST = /Session .+? not found|Target closed|No session/i;

```

This pattern matches errors like:
- `"Session 12345 not found"`
- `"Target closed"`
- `"No session"`

When any CDP response contains an error whose `message` field matches this regex, the runtime flags the condition as a recoverable session loss rather than a permanent failure.

## The Invalidation and Retry Flow

Every CDP request flows through the `browserCdp()` wrapper, which orchestrates detection, cleanup, and recovery. The mechanism operates in **four coordinated stages**:

### 1. Clearing Stale State with `invalidateSession()`

Once a session‑lost error is detected, `invalidateSession()` (lines 46‑53) performs immediate cleanup:

```typescript
// browser-runtime.ts lines 46-53 (conceptual)
function invalidateSession() {
  state.sessionId = null;           // drop the dead session ID
  state.sessionAt = null;           // reset timestamp
  state.dialog = null;              // clear pending dialog state
  // remove page-level event subscriptions...
}

```

This ensures no subsequent call attempts to reuse an invalid session identifier.

### 2. Establishing a Fresh Session with `ensureSession()`

The `ensureSession()` method (lines 107‑139) implements the **re‑attachment logic**:

- Queries available targets via `Target.getTargets`
- Selects an active tab (or respects a preferred target if configured)
- Calls `Target.attachToTarget` to obtain a new `sessionId`
- Enables required domains: `Page.enable`, `Runtime.enable`, `DOM.enable`
- Caches the new session ID and timestamp in runtime state

```typescript
// Typical flow inside ensureSession()
const { sessionId } = await browserCdp(
  'Target.attachToTarget',
  { targetId: activeTarget.targetId, flatten: true },
  undefined,  // no sessionId for browser-level calls
);
state.sessionId = sessionId;
state.sessionAt = Date.now();

```

### 3. The `browserCdp()` Wrapper Orchestrates Retry

The wrapper function (lines 95‑104) surrounds every CDP invocation with the recovery logic:

```typescript
// browser-runtime.ts lines 95-104 (simplified structure)
async function browserCdp(method, params, sessionIdOverride) {
  try {
    return await rawCdp(method, params, effectiveSessionId);
  } catch (error) {
    if (SESSION_LOST.test(error.message)) {
      invalidateSession();
      await ensureSession();           // re-attach
      return rawCdp(method, params, state.sessionId);  // retry
    }
    throw error;  // non-recoverable errors propagate
  }
}

```

The **original request is automatically re‑issued** with the fresh session ID, completely transparent to the caller.

### 4. Proactive Cleanup on Detach Events

Even when CDP calls succeed, the runtime listens for **lifecycle events** that indicate session termination. In `handleMessage()` (lines 52‑66), notifications like `Target.detachedFromTarget` or `Target.targetDestroyed` trigger the same `invalidateSession()` cleanup, keeping internal state consistent before the next request attempts to use the dead session.

## Practical Usage: Recovery Without Boilerplate

The recovery mechanism operates **below the public API surface**. Developers using helpers like `navigate()`, `click()`, or `js()` receive automatic protection without explicit handling:

```typescript
// Navigation that survives a mid-flight session loss
await navigate('https://example.com');
await click('button#submit');  // Internally calls cdp() → browserCdp()

```

```typescript
// JavaScript evaluation with transparent retry
const result = await js(`
  document.title;
`);
// If the session dies during evaluation, ego‑browser recovers and returns the result

```

```typescript
// Low-level CDP access still benefits from the wrapper
const metrics = await browserCdp(
  'Performance.getMetrics',
  {},
  undefined,  // Let runtime resolve session
);

```

## Key Source Files and Responsibilities

| File | Role | Direct Link |
|------|------|-------------|
| [`src/browser-runtime.ts`](https://github.com/citrolabs/ego-lite/blob/main/src/browser-runtime.ts) | Core CDP transport, `browserCdp()` wrapper, `ensureSession()`, `invalidateSession()` | [browser-runtime.ts](https://github.com/citrolabs/ego-lite/blob/main/package/ego-browser/src/browser-runtime.ts) |
| [`src/state.ts`](https://github.com/citrolabs/ego-lite/blob/main/src/state.ts) | Mutable runtime state: `sessionId`, `sessionAt`, `dialog`, target preferences | [state.ts](https://github.com/citrolabs/ego-lite/blob/main/package/ego-browser/src/state.ts) |
| [`src/cdp-eval.ts`](https://github.com/citrolabs/ego-lite/blob/main/src/cdp-eval.ts) | High-level helpers `cdp()` and `js()` that delegate to `browserCdp()` | [cdp-eval.ts](https://github.com/citrolabs/ego-lite/blob/main/package/ego-browser/src/cdp-eval.ts) |
| [`src/ego-errors.ts`](https://github.com/citrolabs/ego-lite/blob/main/src/ego-errors.ts) | Error normalization utilities for the ego bridge | [ego-errors.ts](https://github.com/citrolabs/ego-lite/blob/main/package/ego-browser/src/ego-errors.ts) |

## Summary

- **Pattern-based detection**: The `SESSION_LOST` regex in [`browser-runtime.ts`](https://github.com/citrolabs/ego-lite/blob/main/browser-runtime.ts) identifies recoverable session errors across multiple message variants.
- **Atomic invalidation**: `invalidateSession()` clears all stale state—including session ID, timestamps, and dialog state—to prevent reuse of dead identifiers.
- **Automatic re‑attachment**: `ensureSession()` locates active targets, attaches fresh CDP sessions, and re-enables required domains.
- **Transparent retry**: The `browserCdp()` wrapper catches session‑lost errors, triggers recovery, and re‑issues the original request without caller intervention.
- **Proactive maintenance**: Detach and destroy events from the `Target` domain preemptively clean up state before errors occur.

## Frequently Asked Questions

### How does ego‑browser distinguish between recoverable session loss and permanent CDP errors?

ego‑browser uses the `SESSION_LOST` regular expression to match specific error message patterns like `"Session … not found"` or `"Target closed"`. Errors that do not match this pattern—such as protocol syntax errors or domain-not-enabled failures—are re‑thrown immediately without retry. This selective handling prevents wasted re‑attachment attempts on genuinely unrecoverable conditions.

### What happens if `ensureSession()` cannot find an active target to attach?

If no active tab exists and no preferred target is configured, `ensureSession()` will fail with a clear error after exhausting available targets. The caller receives this failure directly, as it indicates a browser state that automatic recovery cannot resolve (e.g., all tabs closed or browser process terminated).

### Does the retry mechanism introduce race conditions with concurrent CDP calls?

The runtime maintains session state in a centralized `state` object. While `invalidateSession()` and `ensureSession()` are synchronous or awaited sequentially, concurrent callers during recovery may observe transient states. In practice, the `browserCdp()` wrapper's try-catch-retry structure serializes recovery per call, and the shared `sessionId` ensures subsequent calls use the fresh attachment.

### Can I disable automatic session re‑attachment for debugging purposes?

The current implementation in `citrolabs/ego-lite` does not expose a configuration flag to disable recovery. The `browserCdp()` wrapper always applies the retry logic for `SESSION_LOST` matches. To observe raw errors, you would need to patch [`browser-runtime.ts`](https://github.com/citrolabs/ego-lite/blob/main/browser-runtime.ts) locally or invoke `rawCdp()` directly, bypassing the wrapper entirely.