How the Ego-Lite Element-Resolver Classifies Errors as Transient or Permanent
The element-resolver in citrolabs/ego-lite classifies resolution failures by throwing an ElementResolutionError with a kind field of "transient" for retryable issues and "permanent" for unresolvable ambiguities, using the matchCountKind helper in src/element-resolver.ts to assign the category based on how many elements a locator matches.
When a browser automation locator fails in the citrolabs/ego-lite framework, the element-resolver does not simply crash—it categorizes the failure so the caller knows whether to retry or abort. Located in src/element-resolver.ts, the resolver distinguishes between transient errors that may resolve after a fresh snapshot and permanent errors caused by inherently ambiguous selectors. This article explains the exact logic, source code paths, and helper functions that drive that classification.
The ElementResolutionError Kind System
The core mechanism is a custom error class that carries a discriminator property. Instead of a generic exception, every resolution failure raises an ElementResolutionError annotated with a kind value.
Error Class Definition
In src/element-resolver.ts, the ElementResolutionError class stores a kind field that is typed as "transient" or "permanent". This field is populated at the throw site and later inspected by the driver to decide whether a re-snapshot is worthwhile.
The matchCountKind Helper
The classification logic is centralized in the matchCountKind helper around lines 46-49. According to the citrolabs/ego-lite source code, this function accepts a numeric match count and returns the corresponding error kind:
function matchCountKind(n) {
return n > 1 ? 'permanent' : 'transient';
}
When a locator matches more than one element, the helper returns "permanent" because retrying the same selector cannot disambiguate the result without changing the locator itself. For all other counts—including zero—it returns "transient", since the element may appear or become unique after the page state updates.
Transient vs. Permanent Classification Rules
The resolver applies these kinds at distinct throw sites throughout src/element-resolver.ts. The distinction determines whether the browser runtime should re-snapshot or immediately propagate the failure.
Transient Error Scenarios
Transient errors represent temporary conditions that might succeed after a page refresh or retry. According to the source analysis, the resolver emits "transient" for cases such as:
- Unknown reference IDs.
- Zero-match selectors.
- Degenerated box-model calculations.
- Any scenario where a single element cannot yet be identified unambiguously but the DOM might change.
These explicit "transient" throws appear at multiple points in the file, including around lines 74, 143, and 298.
Permanent Error Scenarios
Permanent errors signal structural problems with the locator itself. The primary trigger is an ambiguous selector that matches two or more elements. When the candidate collection contains multiple nodes, the resolver invokes matchCountKind and throws an ElementResolutionError with kind set to "permanent". Because the underlying DOM structure satisfies the locator too broadly, waiting or retrying will not fix the issue—the locator itself must be narrowed.
Runtime Resolution Flow
During execution, the resolver collects candidate elements, evaluates the result count, and either invokes matchCountKind or throws a known transient error directly. As implemented in citrolabs/ego-lite, the surrounding driver logic in src/browser-runtime.ts treats a transient ElementResolutionError as a signal to re-snapshot the page and attempt resolution again. Permanent errors bypass the retry loop entirely and propagate to the caller immediately, preventing wasted polling cycles on unresolvable ambiguities.
Code Examples
The following patterns show how to consume the kind field in application code.
Retry on Transient Errors
try {
const el = await resolveElement('loc=css:#submit-button');
await el.click();
} catch (e) {
if (e instanceof ElementResolutionError && e.kind === 'transient') {
// Re-snapshot the page and try again
await ego.refresh();
// ... retry the resolution ...
} else {
throw e; // permanent or unrelated error
}
}
Abort on Permanent Errors
try {
const el = await resolveElement('loc=css:.ambiguous-item');
} catch (e) {
if (e.kind === 'permanent') {
console.error('Locator matches multiple elements - adjust the selector');
// Abort or fallback to a different strategy
}
}
Helper That Distinguishes Both Kinds
async function clickIfUnique(locator) {
try {
const el = await resolveElement(locator);
await el.click();
return true;
} catch (e) {
if (e.kind === 'permanent') {
console.warn(`Cannot click: ${locator} is ambiguous`);
}
return false;
}
}
Summary
ElementResolutionErrorinsrc/element-resolver.tscarries akindfield that is either"transient"or"permanent".- The
matchCountKindhelper classifies multi-element matches as"permanent"and everything else as"transient". - Transient errors cover zero-match selectors, unknown references, and degenerated box-model states that may resolve after a retry.
- Permanent errors indicate locator ambiguity that requires changing the selector rather than re-snapshotting.
- The ego-lite runtime uses this classification to decide whether to re-snapshot and retry or to propagate the error immediately.
Frequently Asked Questions
What is the difference between transient and permanent errors in ego-lite?
Transient errors are temporary resolution failures—such as missing elements or unknown reference IDs—that may disappear after the page is re-snapshotted or retried. Permanent errors are structural ambiguities, typically caused by a locator matching multiple elements, that cannot be resolved by waiting and require a corrected selector.
How does matchCountKind decide the error kind?
As defined in src/element-resolver.ts, matchCountKind accepts a numeric match count and returns "permanent" when the count is greater than one. For zero or one matches, it returns "transient" because the condition might change on a subsequent attempt.
Can a transient error become permanent after retries?
The classification is computed independently for each resolution attempt. A transient error such as a zero-match does not automatically convert to a permanent error after retries. If the DOM later changes so that the same locator matches multiple elements, a new resolution attempt would classify that subsequent failure as permanent.
Where does the retry logic for transient errors live?
The retry logic resides in the browser runtime layer, specifically src/browser-runtime.ts. It catches ElementResolutionError instances, checks for kind === 'transient', and triggers a re-snapshot and retry loop. Permanent errors are intentionally allowed to propagate without retrying.
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 →