How Ego-Lite Handles Stale or Missing CDP Sessions: Automatic Recovery and TTL Management
Ego-Lite automatically detects and recovers from stale or missing Chrome DevTools Protocol (CDP) sessions using TTL-based validation, in-flight request deduplication, and transparent error retry logic implemented in browser-runtime.ts.
Ego-Lite is an open-source browser automation framework maintained by CitroLabs that manages browser interactions through a single active CDP session architecture. Understanding how the library handles stale or missing CDP sessions is essential for building resilient AI agent workflows that require reliable browser control without manual connection management.
Session Lifecycle Management in browser-runtime.ts
The core session state is maintained in package/ego-browser/src/browser-runtime.ts, where the ensureSession() function serves as the gateway for all CDP operations. This helper validates existing sessions or creates fresh ones using a time-to-live (TTL) mechanism.
TTL-Based Session Validation
Every CDP-dependent operation begins with a staleness check. If state.sessionId exists and the elapsed time since state.sessionAt is less than SESSION_TTL_MS, the existing session is reused immediately.
if (state.sessionId && Date.now() - state.sessionAt < SESSION_TTL_MS) {
return state.sessionId; // ✅ reuse fresh session
}
This validation appears at lines 107-110 in browser-runtime.ts, ensuring that only valid, recently-used sessions handle new requests.
In-Flight Request Deduplication
To prevent race conditions during session creation, Ego-Lite implements an in-flight promise pattern. When ensureSession() detects a session creation is already underway (state.sessionInflight), it returns the pending promise instead of initiating a duplicate Target.attachToTarget call.
if (state.sessionInflight) return state.sessionInflight;
state.sessionInflight = (async () => { … })();
This mechanism, found at lines 111-114, serializes concurrent requests while allowing parallel operations to share the same session establishment workflow.
Attaching to Browser Targets
When validation fails or no session exists, Ego-Lite enumerates available tabs via listTabs(), selects the active or preferred target, and attaches a new session:
const result = assertNoEgoError(await browserEgo().listTabs());
const tabs = result?.tabs || result?.targetInfos || [];
const active = preferred || tabs.find(t => t.active) || tabs[tabs.length‑1];
const attached = await rawCdp("Target.attachToTarget", {targetId, flatten:true});
state.sessionId = attached.result?.sessionId || attached.sessionId;
state.sessionTargetId = targetId;
After successful attachment (lines 116-136), the system calls enablePageEvents() and updates state.sessionAt to reset the TTL clock.
Automatic Recovery from Stale or Missing Sessions
Beyond proactive TTL checking, Ego-Lite implements reactive recovery for unexpected session loss.
Error Detection and Retry Logic
The cdp() wrapper intercepts errors matching the SESSION_LOST regex. For implicit requests (those without an explicit session ID), Ego-Lite automatically invalidates the stale session and retries the operation:
const lost = SESSION_LOST.test(error?.message || "");
if (lost && !explicit && !BROWSER_LEVEL(method)) {
invalidateSession();
const fresh = await ensureSession();
return rawCdp(method, params, fresh, timeoutMs);
}
This retry logic at lines 96-103 ensures that transient connection failures do not propagate to calling code.
Explicit Session Invalidation
When the browser signals target destruction through Target.detachedFromTarget or Target.targetDestroyed events, the invalidateSession() function clears state.sessionId, state.sessionTargetId, and related bookkeeping:
if (targetId && targetId === state.sessionTargetId) {
invalidateSession(); // 🔄 session lost → refresh
}
Located at lines 60-64, this cleanup prevents ghost sessions from interfering with subsequent operations.
Practical Implementation Examples
Ego-Lite abstracts session management behind high-level helpers, but also exposes low-level control when needed.
To perform actions without managing sessions manually:
// High-level helper automatically ensures valid session
await egoBrowser.click('button.submit');
To force a fresh session or access raw CDP:
// Manually ensure session (rarely needed)
const sessionId = await ensureSession();
// Use session ID with raw CDP calls
await rawCdp('Runtime.evaluate', {expression: 'document.title'}, sessionId);
The global state tracking these values is defined in package/ego-browser/src/state.ts, while CDP command wrappers reside in package/ego-browser/src/cdp-eval.ts.
Summary
- TTL-based validation in
ensureSession()prevents the use of stale connections by checkingstate.sessionAtagainstSESSION_TTL_MS. - In-flight deduplication via
state.sessionInflighteliminates duplicateTarget.attachToTargetcalls during concurrent operations. - Automatic error recovery catches
SESSION_LOSTerrors, invalidates the dead session, and transparently retries with fresh credentials. - Explicit invalidation responds to browser-initiated detachment events by clearing global state in
invalidateSession(). - Single active session architecture simplifies state management while ensuring all CDP operations execute against a live target.
Frequently Asked Questions
What triggers a stale CDP session in Ego-Lite?
A session becomes stale when the elapsed time since its last use exceeds SESSION_TTL_MS or when the browser terminates the target via Target.targetDestroyed. The ensureSession() function detects TTL expiration by comparing Date.now() against state.sessionAt at lines 107-110 of browser-runtime.ts.
How does Ego-Lite handle concurrent requests during session creation?
Ego-Lite uses an in-flight promise pattern stored in state.sessionInflight. When multiple helpers call ensureSession() simultaneously, subsequent invocations receive the same pending promise rather than triggering redundant Target.attachToTarget calls, as implemented at lines 111-114.
Can developers manually force a new CDP session?
Yes. Developers can call invalidateSession() to clear the current state.sessionId and state.sessionTargetId, then invoke ensureSession() to establish a fresh connection. However, this is rarely necessary since the library automatically creates new sessions when TTL expires or errors occur.
What happens if the browser rejects the attachment request during recovery?
If Target.attachToTarget fails during the recovery sequence, the error propagates through the rawCdp() wrapper without additional retries, allowing calling code to handle catastrophic connection failures. Transient session errors matching SESSION_LOST trigger exactly one automatic retry with a freshly attached session before surfacing persistent failures to the caller.
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:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →