How Ego-Lite Classifies Element-Resolution Errors as Transient or Permanent

Ego-Lite uses a kind field on the ElementResolutionError class to distinguish transient from permanent failures, with transient errors triggering retries and permanent errors causing immediate abort.

The error classification system in ego-lite determines whether an element-resolution failure might resolve on retry or is fundamentally unrecoverable. This decision logic lives primarily in src/element-resolver.ts and drives the library's automatic retry behavior.

The ElementResolutionError Foundation

Every resolution failure flows through the ElementResolutionError class. The constructor at lines 4–10 accepts a kind parameter that must be either "transient" or "permanent":

class ElementResolutionError extends Error {
  kind: "transient" | "permanent";
  
  constructor(message: string, kind: "transient" | "permanent") {
    super(message);
    this.kind = kind;
  }
}

Callers in src/driver/waits.ts and src/driver/locator.ts inspect err.kind to decide whether to retry or abort.

How Transient Failures Are Identified

Transient errors represent temporary conditions that may change on subsequent attempts. The resolver marks these as "transient" in several scenarios.

Unknown or Missing Refs

When refMap.get(refId) returns undefined, the resolver throws a transient error (lines 73–75):

if (!entry) {
  throw new ElementResolutionError(`Unknown ref: ${refId}`, "transient");
}

The ref may become available after the page re-snapshots.

Box-model problems trigger transient errors when a node's box model is missing or has zero size (lines 55–62). The element likely exists in the DOM but hasn't rendered yet.

Role/name lookup failures via findBackendNodeIdByRoleName (lines 18–22) are also transient—the UI may still be updating.

Zero Matches for Single-Element Locators

When a locator resolves to zero elements, the error is transient (lines 36–42):

if (matches.length === 0) {
  throw new ElementResolutionError(
    `No element found for locator: ${locator}`,
    "transient"
  );
}

How Permanent Failures Are Identified

Permanent errors denote unrecoverable problems that will never succeed regardless of retries.

Invalid Selectors and Malformed Queries

Errors from malformed selector syntax are marked permanent (lines 85–89):

throw new ElementResolutionError(
  `Invalid selector: ${selector}: ${message}`,
  "permanent"
);

Ambiguous Locators

When a locator matches more than one element, the error is permanent because the locator is ambiguous. The matchCountKind helper (lines 46–50) implements this logic:

function matchCountKind(message: string): "transient" | "permanent" {
  const m = /matched (\d+)/.exec(message);
  const n = m ? Number(m[1]) : 0;
  return n > 1 ? "permanent" : "transient";
}

A match count of 1 yields transient (might stabilize), while >1 yields permanent (fundamentally ambiguous).

Missing Essential Backend IDs

When an accessibility node lacks a backendDOMNodeId (lines 58–62), the error is permanent: the element cannot be addressed via Chrome DevTools Protocol.

Selector Resolution Error Pattern

The selectorResolutionError function (lines 52–60) demonstrates the classification pipeline:

  1. Inspect the CDP evaluation message
  2. Extract match count using regex
  3. Delegate to matchCountKind for the final decision
  4. Fall back to permanent for invalid selectors
function selectorResolutionError(message: string): ElementResolutionError {
  if (message.includes("matched")) {
    const kind = matchCountKind(message);
    return new ElementResolutionError(message, kind);
  }
  return new ElementResolutionError(message, "permanent");
}

Error Consumption in the Driver

The classification only matters because downstream code acts on it. In src/driver/waits.ts, retry loops check err.kind === "transient" before continuing. Permanent errors break immediately. This pattern prevents wasted retries on impossible selectors while allowing tolerance for timing-sensitive DOM operations.

Summary

  • Transient errors (ref unavailable, zero matches, box-model missing, single match ambiguity) indicate conditions that may resolve on retry
  • Permanent errors (invalid selectors, multiple matches, missing backend IDs) represent fundamental failures that never succeed
  • The ElementResolutionError class in src/element-resolver.ts carries the kind discriminator
  • Resolution logic inspects match counts, ref existence, and DOM state to assign the correct classification
  • Callers in src/driver/waits.ts and src/driver/locator.ts consume kind to drive retry behavior

Frequently Asked Questions

What makes an element-resolution error transient in Ego-Lite?

A transient error occurs when the failure might resolve on a subsequent attempt. Common causes include: the element hasn't rendered yet (missing box model), the ref isn't in the current snapshot, or a locator matches zero elements. The system assumes these conditions are timing-dependent and may stabilize.

When does Ego-Lite classify an error as permanent?

Permanent errors indicate unrecoverable problems: invalid selector syntax, a locator matching multiple elements (ambiguity), or an accessibility node without a backendDOMNodeId. These failures cannot succeed regardless of how many times the operation retries.

How does the match count determine error classification?

The matchCountKind function in src/element-resolver.ts parses CDP messages for "matched N elements". N = 1 yields transient (the match might stabilize to unique), while N > 1 yields permanent (the locator is fundamentally ambiguous). This prevents endless retries on selectors that will always match multiple elements.

Where is error classification consumed in the codebase?

The kind property is read in src/driver/waits.ts to control retry loops and in src/driver/locator.ts to propagate retryable versus fatal failures. This separation allows the resolver to focus on classification while driver code handles policy.

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 →