How to Debug ego-lite Applications: CLI Flags, CDP Tracing, and Best Practices

Enable --debug-clicks or EGO_BROWSER_DEBUG_CLICKS=1 to see per-click logging, use EGO_BROWSER_DEBUG_CDPMESSAGES=1 to inspect raw Chrome DevTools Protocol traffic, and leverage console.log captured by the output sink for ad-hoc state inspection.

ego-lite executes user-supplied JavaScript inside the ego-browser harness, routing all output through a centralized console.log sink. When automation scripts fail, the runtime provides multiple built-in mechanisms to inspect state, trace CDP traffic, and diagnose element resolution issues. This guide covers the complete debugging toolkit based on the citrolabs/ego-lite source code.


Debug Flags and Environment Variables

The entry point in src/run.ts parses CLI flags and environment variables that control logging verbosity. These are the primary levers for debugging ego-lite applications.

--debug-clicks for Per-Click Visibility

Set this flag to log every click resolution, including the resolved DOM element and click coordinates.


# CLI flag

node dist/out/index.js --debug-clicks < script.js

# Or environment variable

EGO_BROWSER_DEBUG_CLICKS=1 echo "await page.click('@5')" | node dist/out/index.js

The flag handling appears at lines 85-86 in src/run.ts, which injects the debug context into the runtime before script execution.

Expected output format:


[debug] click resolved to backendNodeId 42 (CSS selector "a.more")
[debug] click coordinates: x=34 y=12

EGO_BROWSER_DEBUG_CDPMESSAGES for Raw CDP Traffic

Set this environment variable to stream every Chrome DevTools Protocol request and response. This is essential when verifying that the correct CDP methods are being invoked.

EGO_BROWSER_DEBUG_CDPMESSAGES=1 node dist/out/index.js < script.js

Sample output showing the request-response cycle:


→ Runtime.evaluate {"expression":"document.title"}
← Runtime.evaluate {"result":{"type":"string","value":"Example Domain"}}
→ Page.screenshot {"path":"/tmp/shot.png"}
← Page.screenshot {"data":"iVBORw0KGgoAAAANSUhEUg..."}

This low-level tracing is implemented in src/browser-runtime.ts, which manages the CDP transport and session lifecycle.


Using console.log and the Output Sink

All console.log calls from your script are captured by src/output-sink.ts, which buffers and formats output before it reaches the host. This makes console.log the primary tool for ad-hoc debugging.

Basic State Inspection

const info = await page.info()
console.log('Page info →', info)

const title = await page.evaluate('document.title')
console.log('Current title:', title)

The output sink ensures these logs appear in the CLI stream with proper formatting and timestamps.

Overriding the Sink with --doctor

The --doctor flag reconfigures the output sink to dump raw CDP messages or additional metadata, useful when the default formatting obscures critical details.


Debugging Element Resolution Failures

Element resolution is handled by src/element-resolver.ts, which converts locators (@N, loc=css:..., xpath=...) to CDP object IDs. When resolution fails, the system classifies errors and prints diagnostic messages.

Common Error Types

Error Class Meaning Resolution
Transient Element not yet in DOM Add await page.waitForSelector()
Permanent Invalid or malformed selector Check locator syntax in error message
Stale ref Element removed after snapshot Call await page.snapshot() to refresh

Force Ref-Map Refresh with page.snapshot()

DOM mutations invalidate the internal ref-map. When you encounter "ref-not-found" errors, explicitly refresh the snapshot:

await page.navigate('https://example.com')
await page.click('button#load-more')   // triggers AJAX content load
await page.snapshot()                  // forces fresh ref-map
await page.click('@7')                 // now resolves to newly added element

The snapshot method is exposed in src/helpers.ts as part of the page.* API surface.


Task-Space Diagnostics

Task spaces isolate browser sessions and state. When spaces hang or disappear, use the taskSpaces.* helpers for introspection.

Listing and Claiming Spaces

// Dump all known task spaces
console.log('All spaces:', await taskSpaces.list())

// Attach to specific space for debugging
await taskSpaces.claimTaskSpace('my-space')
console.log('Current space:', await taskSpaces.current())

// Take over a space that may be in bad state
await taskSpaces.takeOverTaskSpace('my-space')

The underlying state structures are defined in src/state.ts, which tracks space registry and session bindings.


Running the Test Harness with Debug Output

The repository's end-to-end tests in src/taskspace-e2e.test.mjs exercise the full stack and can be run with verbose logging.

DEBUG=* npm test

The DEBUG=* environment variable activates every internal debug() call, showing:

  • Resolver attempts and selector parsing
  • Session re-attachment events
  • Event-queue statistics

This provides the same diagnostic output as production runs, but with assertion safety.

Watch Mode for Iterative Debugging

The test runner automatically rebuilds and reruns .test.mjs files on changes. This is the fastest way to iterate on fixes while observing debug output.


Complete Debugging Workflows

Workflow 1: Investigating a Failing Click

// click-debug.js
await page.navigate('https://example.com')
await page.waitForSelector('a.more')
await page.click('@12')   // suspect selector

Execute with full visibility:

EGO_BROWSER_DEBUG_CLICKS=1 \
EGO_BROWSER_DEBUG_CDPMESSAGES=1 \
node dist/out/index.js < click-debug.js

Workflow 2: Verifying CDP Method Calls

// cdp-verify.js
await page.evaluate('document.title')
await page.screenshot({ path: '/tmp/shot.png' })

Trace the exact protocol exchange:

EGO_BROWSER_DEBUG_CDPMESSAGES=1 node dist/out/index.js < cdp-verify.js

Workflow 3: Post-AJAX Element Interaction

// ajax-handler.js
await page.navigate('https://example.com')
await page.click('button#load-more')
await page.snapshot()      // critical: refresh after DOM change
const items = await page.evaluate(() => document.querySelectorAll('.item').length)
console.log('Items after load:', items)
await page.click('@7')     // interact with new element

Key Source Files for Debugging Reference

File Purpose
src/run.ts CLI flag parsing, debug context injection, console redirection
src/output-sink.ts Log buffering, formatting, --doctor override support
src/helpers.ts Public API surface (page.*, browser.*, taskSpaces.*)
src/element-resolver.ts Locator parsing, error classification, diagnostic messages
src/browser-runtime.ts CDP transport, session lifecycle, message tracing
src/state.ts Task-space and session state data structures
src/taskspace-e2e.test.mjs Reference e2e implementation with debug hooks

Summary

  • Use --debug-clicks or EGO_BROWSER_DEBUG_CLICKS=1 to see exactly which element is being clicked and where
  • Enable EGO_BROWSER_DEBUG_CDPMESSAGES=1 to trace raw CDP traffic and verify protocol-level behavior
  • Leverage console.log with the output sink for ad-hoc state inspection; all logs are captured and formatted by src/output-sink.ts
  • Call page.snapshot() after DOM mutations to refresh the ref-map and prevent stale selector errors
  • Inspect task-space state with taskSpaces.list() and taskSpaces.takeOverTaskSpace() when sessions hang or disappear
  • Run DEBUG=* npm test to see detailed internal logs during end-to-end test execution

Frequently Asked Questions

How do I see which element is actually being clicked?

Enable per-click logging with the --debug-clicks flag or EGO_BROWSER_DEBUG_CLICKS=1 environment variable. According to the ego-lite source code in src/run.ts, this prints the resolved backendNodeId, CSS selector, and click coordinates for every page.click() call.

Why does my selector work sometimes but fail other times?

This indicates a stale ref-map. The element resolver in src/element-resolver.ts maintains a mapping of @N references to CDP object IDs. When JavaScript modifies the DOM, these references invalidate. Insert await page.snapshot() before the failing operation to force a refresh.

Can I see the raw Chrome DevTools Protocol messages?

Yes. Set EGO_BROWSER_DEBUG_CDPMESSAGES=1 before running your script. The src/browser-runtime.ts file implements this tracing, streaming every CDP request and response through the output sink. This is invaluable for verifying that Runtime.evaluate, Page.screenshot, and other methods receive correct parameters.

How do I debug a task-space that appears to hang?

Use the taskSpaces.* helpers from src/helpers.ts. First call await taskSpaces.list() to see all registered spaces, then await taskSpaces.takeOverTaskSpace('name') to attach and inspect internal state. The underlying registry is maintained in src/state.ts.

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 →