ego-lite Element Resolution System: How Transient vs Permanent Failures Work

ego-lite uses a kind property on ElementResolutionError to distinguish retryable ("transient") failures from non-retryable ("permanent") failures, letting the runtime decide whether to poll or abort.

The ego-lite element resolution system is the core mechanism that locates DOM elements through refs, selectors, and role-based locators. When resolution fails, the system categorizes the failure into one of two buckets—each with drastically different runtime behavior. This article breaks down how citrolabs/ego-lite implements this distinction and why it matters for building reliable browser automation.

How ElementResolutionError Categorizes Failures

All element-resolution failures in ego-lite flow through ElementResolutionError, defined in package/ego-browser/src/element-resolver.ts. The error carries a kind property that is either "transient" or "permanent".

Transient Failures: Retryable by Design

Transient failures indicate the lookup might succeed later. The runtime responds by retrying until the element appears or a timeout expires.

Common transient scenarios include:

  • Missing box modelresolveElementCenter throws transient when boxModel is absent or malformed (content array has fewer than 8 points) because the element hasn't rendered yet.

  • Zero matches — A role locator or selector that returns no elements is treated as transient, assuming the DOM will update.

  • Unknown reference ID — When a ref points to a node not yet tracked, the system assumes it will arrive.

// From src/element-resolver.ts — transient due to missing render
if (!boxModel || boxModel.content.length < 8) {
  throw new ElementResolutionError(
    "Element has no box model (not rendered or zero-sized)",
    "transient",
  );
}

Permanent Failures: Abort Immediately

Permanent failures signal unrecoverable conditions where retrying would never succeed. The runtime propagates these immediately.

Common permanent scenarios include:

  • Invalid selector syntax — A malformed selector string cannot be evaluated.

  • Multiple element matches — When single-element resolution is required but the locator is ambiguous.

  • Missing backend DOM node ID — AX nodes without backendDOMNodeId cannot be mapped to actual DOM elements.

// Ambiguous match → permanent failure
if (/\bmatched \d+ elements\b/.test(message)) {
  return new ElementResolutionError(message, "permanent");
}
return new ElementResolutionError(
  `Invalid selector: ${selector}: ${message}`,
  "permanent",
);

Runtime Behavior: Where the Distinction Matters

The kind property drives retry logic throughout ego-lite's driver layer.

Poll-and-Retry Loops

In src/driver/waits.ts, the wait helpers explicitly check err.kind before deciding whether to continue polling:

// From src/driver/waits.ts lines 505-506
if (err instanceof ElementResolutionError && err.kind === "transient") {
  // Continue polling until timeout
} else {
  // Permanent error — abort immediately
  throw err;
}

This pattern prevents wasted cycles on unrecoverable errors while allowing legitimate timing issues to resolve naturally.

Locator Fallbacks

src/driver/locator.ts uses the error kind to decide whether alternative resolution strategies should be attempted. A transient failure might trigger a different selector approach; a permanent one halts the chain.

Key Files in the Resolution Pipeline

File Responsibility
package/ego-browser/src/element-resolver.ts Defines ElementResolutionError and implements all lookup logic with kind assignment.
package/ego-browser/src/driver/waits.ts Implements retry loops that branch on err.kind === "transient".
package/ego-browser/src/driver/locator.ts Consumes resolution errors to drive fallback and error-handling strategies.

Summary

  • ego-lite element resolution returns failures categorized by recoverability, not just error messages.
  • Transient failures (kind: "transient") trigger automatic retry until success or timeout—ideal for race conditions with rendering.
  • Permanent failures (kind: "permanent") abort immediately since retrying would never help—used for invalid selectors and ambiguous matches.
  • The kind property is checked in src/driver/waits.ts and src/driver/locator.ts to determine runtime behavior.

Frequently Asked Questions

What triggers a transient vs permanent error in ego-lite?

Transient errors occur when an element simply isn't available yet—missing box models, zero matches, or unknown refs. Permanent errors indicate fundamental problems: invalid selector syntax, multiple matches when one is required, or AX nodes lacking backend DOM IDs.

How does ego-lite decide whether to retry a failed element lookup?

The runtime inspects err.kind on ElementResolutionError. If kind === "transient", polling loops in src/driver/waits.ts continue until timeout. Any other kind aborts the operation immediately.

Can I override the error kind in ego-lite's element resolver?

The kind is set internally based on failure mode detection in src/element-resolver.ts. Custom resolution logic would need to construct ElementResolutionError with the appropriate kind string to participate in the retry/abort protocol correctly.

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 →