# Transient vs Permanent Element Resolution Failures in ego-lite: A Complete Guide

> Understand transient vs permanent element resolution failures in ego-lite. Learn how the kind property dictates retries or immediate aborts for automation.

- Repository: [CitroLabs/ego-lite](https://github.com/citrolabs/ego-lite)
- Tags: deep-dive
- Published: 2026-07-31

---

**In ego-lite, element resolution failures are categorized as either transient (retryable) or permanent (non-retryable) via the `kind` property on `ElementResolutionError`, determining whether the automation runtime will retry the lookup or abort immediately.**

When automating browser interactions with **citrolabs/ego-lite**, the framework must gracefully handle cases where DOM elements are not immediately available or cannot be located. The library distinguishes between temporary conditions that might resolve on their own and fundamental errors that will persist regardless of timing. This distinction is encapsulated in the `ElementResolutionError` class and its `kind` property, which drives retry logic throughout the browser automation pipeline.

## Understanding ElementResolutionError and the `kind` Property

At the heart of ego-lite’s error handling lies the `ElementResolutionError` class defined in [`src/element-resolver.ts`](https://github.com/citrolabs/ego-lite/blob/main/src/element-resolver.ts). This error object carries a **`kind`** property that accepts two string literals: `"transient"` or `"permanent"`. The runtime inspects this property to determine whether a failed element lookup warrants a retry or should surface immediately as a hard failure.

The architecture separates these concerns to prevent unnecessary polling when success is impossible (permanent errors) while allowing robust waiting mechanisms for timing-sensitive operations (transient errors).

## Transient Element Resolution Failures

**Transient failures** represent retryable conditions where the element might become available if the automation waits briefly. These errors signal that the lookup logic is sound, but the DOM state is not yet ready.

### Common Causes of Transient Failures

According to the source code in [`src/element-resolver.ts`](https://github.com/citrolabs/ego-lite/blob/main/src/element-resolver.ts), transient errors occur in three primary scenarios:

- **Missing box model**: When `resolveElementCenter` detects that an element lacks a box model (indicating it has not rendered or is zero-sized), it throws a transient error at lines 55-62.
- **Zero locator matches**: Role-based or selector-based locators that return no matches produce a transient failure at lines 46-50.
- **Unknown reference IDs**: Attempts to resolve a ref ID that does not yet exist in the internal registry trigger transient errors at lines 73-76.

### Runtime Behavior and Retry Logic

Callers implementing retry loops—such as `waitForSelector` or ref fallback mechanisms—automatically repeat the lookup when they catch a transient error. The driver code in [`src/driver/waits.ts`](https://github.com/citrolabs/ego-lite/blob/main/src/driver/waits.ts) explicitly checks for this condition at lines 505-506:

```typescript
if (err instanceof ElementResolutionError && err.kind === "transient") {
  // Retry logic continues polling until timeout
}

```

This pattern allows ego-lite to wait for dynamic content, SPA navigation, or lazy-loaded elements without failing prematurely.

## Permanent Element Resolution Failures

**Permanent failures** indicate non-retryable conditions where repeating the lookup would not change the outcome. These errors represent configuration problems, ambiguous selectors, or fundamental mismatches between the locator and the DOM structure.

### Common Causes of Permanent Failures

The [`src/element-resolver.ts`](https://github.com/citrolabs/ego-lite/blob/main/src/element-resolver.ts) file identifies several permanent error conditions at specific line ranges:

- **Invalid selectors**: Syntax errors or unevaluatable selector strings trigger permanent failures at lines 85-90.
- **Ambiguous matches**: When a locator returns multiple elements but the operation requires a single unique match, the resolver throws a permanent error at lines 34-38.
- **Missing backend DOM node IDs**: Accessibility (AX) nodes that lack a `backendDOMNodeId` cannot be mapped to actual DOM nodes, resulting in a permanent error at lines 58-62.

### Why Permanent Errors Abort Immediately

Unlike transient errors, permanent failures propagate immediately to the calling agent. The runtime assumes that invalid selectors or ambiguous locators require code changes rather than additional wait time. For example, [`src/driver/locator.ts`](https://github.com/citrolabs/ego-lite/blob/main/src/driver/locator.ts) uses this distinction to decide whether to attempt fallback strategies or abort the operation entirely.

## Code Examples: Handling Each Failure Type

The following examples from the ego-lite source demonstrate how the framework categorizes and handles these errors.

**Throwing a transient error when the box model is missing:**

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

```

**Throwing a permanent error for ambiguous or invalid selectors:**

```typescript
// src/element-resolver.ts
if (/\bmatched \d+ elements\b/.test(message)) {
  // Multiple matches when single required
  return new ElementResolutionError(message, "permanent");
}
return new ElementResolutionError(
  `Invalid selector: ${selector}: ${message}`,
  "permanent",
);

```

**Driver-level retry logic distinguishing error kinds:**

```typescript
// src/driver/waits.ts
try {
  await waitForSomething();
} catch (err) {
  if (err instanceof ElementResolutionError && err.kind === "transient") {
    // Continue retry loop
  } else {
    // Abort immediately for permanent failures
    throw err;
  }
}

```

## Summary

- **Transient failures** in ego-lite indicate retryable conditions such as missing box models, zero locator matches, or unknown reference IDs, allowing the runtime to poll until success or timeout.
- **Permanent failures** represent non-retryable errors including invalid selectors, ambiguous multiple matches, or missing backend DOM node IDs, causing immediate abortion of the operation.
- The **`ElementResolutionError`** class in [`src/element-resolver.ts`](https://github.com/citrolabs/ego-lite/blob/main/src/element-resolver.ts) encapsulates this distinction via its `kind` property, which [`src/driver/waits.ts`](https://github.com/citrolabs/ego-lite/blob/main/src/driver/waits.ts) inspects at lines 505-506 to determine retry eligibility.
- **Higher-level helpers** like those in [`src/driver/locator.ts`](https://github.com/citrolabs/ego-lite/blob/main/src/driver/locator.ts) use this categorization to decide between fallback strategies and hard failures.

## Frequently Asked Questions

### What triggers a transient element resolution failure in ego-lite?

Transient failures occur when an element cannot be located due to timing or rendering state rather than configuration errors. Specific triggers include elements lacking a box model (not yet rendered), locators returning zero matches, or attempts to resolve unknown reference IDs. These conditions are implemented in [`src/element-resolver.ts`](https://github.com/citrolabs/ego-lite/blob/main/src/element-resolver.ts) and allow the runtime to retry the lookup.

### When does ego-lite throw a permanent element resolution error?

Permanent errors occur when the lookup mechanism itself is flawed or ambiguous. This includes syntactically invalid selector strings, locators matching multiple elements when a single unique element is required, and accessibility nodes that cannot be mapped to DOM nodes due to missing `backendDOMNodeId` values. These errors abort immediately without retry.

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

The decision occurs in driver code such as [`src/driver/waits.ts`](https://github.com/citrolabs/ego-lite/blob/main/src/driver/waits.ts), which catches `ElementResolutionError` instances and inspects the `kind` property. If `kind === "transient"`, the driver continues its retry loop; otherwise, it re-throws the error immediately. This pattern prevents wasted cycles on permanently unresolvable conditions.

### Can a transient error become permanent in ego-lite?

No, the classification is determined at the moment of error creation based on the specific failure condition. However, a transient error that persists beyond the configured timeout will eventually cause the operation to fail, though it remains classified as transient. The error type does not mutate; instead, the runtime simply stops retrying and surfaces the last transient error encountered.