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-clicksorEGO_BROWSER_DEBUG_CLICKS=1to see exactly which element is being clicked and where - Enable
EGO_BROWSER_DEBUG_CDPMESSAGES=1to trace raw CDP traffic and verify protocol-level behavior - Leverage
console.logwith the output sink for ad-hoc state inspection; all logs are captured and formatted bysrc/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()andtaskSpaces.takeOverTaskSpace()when sessions hang or disappear - Run
DEBUG=* npm testto 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:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →