Skip to main content

Structured Failure Feedback

When a UI Bridge action fails, it does not throw a string. It returns a stable machine-readable code, the context the failure happened in, and a ranked list of recovery suggestions — so an agent can decide what to do next without parsing prose.

The code vocabulary is generated from diagnostics/codes.json into packages/ui-bridge/src/diagnostics/codes.generated.ts. That file is the single source of truth for the codes, their descriptions, their common causes and their recovery templates.

Error Codes​

Every code is UB--prefixed and belongs to one of five categories: element, action, assertion, network, system. There are 51:

CodeCategoryDescription
UB-ACTION-FAILEDactionThe action could not be completed.
UB-ACTION-REJECTEDactionThe action was rejected before execution (e.g. by a guard, policy, or validation gate).
UB-ACTION-TIMEOUTactionThe action timed out waiting for a condition to be met.
UB-AMBIGUOUS-MATCHelementMultiple elements match with similar confidence; a single best match could not be chosen.
UB-ASSERT-CONTRASTassertionA color-contrast (accessibility) assertion failed.
UB-ASSERT-ELEMENT-MISSINGassertionAn assertion targeted an element that does not exist.
UB-ASSERT-LAYOUTassertionA layout/geometry assertion failed (position, size, alignment, overlap).
UB-ASSERT-TEXT-MISMATCHassertionAn assertion about element text content failed (exact/contains/regex mismatch).
UB-ASSERT-TIMEOUTassertionAn assertion timed out waiting for its condition.
UB-ASSERT-VISIBILITYassertionAn assertion about element visibility failed.
UB-CAPABILITY-UNAVAILABLEsystemThe running build does not serve this capability (e.g. a runner-direct /vision/* route called on an SDK-only bridge). Nothing was attempted; this is not an action rejection.
UB-ELEM-BLOCKEDelementThe element is blocked by another element such as a modal, overlay, or popup.
UB-ELEM-DISABLEDelementThe element is disabled and cannot be interacted with.
UB-ELEM-NOT-ENABLEDelementThe element is present and visible but disabled, so it cannot be interacted with.
UB-ELEM-NOT-FOUNDelementNo element matching the target description or selector could be found.
UB-ELEM-NOT-INTERACTABLEelementThe element is visible and enabled but cannot receive the interaction (e.g. covered, animating, or pointer-events:none).
UB-ELEM-NOT-VISIBLEelementThe element exists in the DOM but is not currently visible.
UB-HEALTH-EMPTY-CONTENT-AREAsystemPage health: the main content area is nearly empty while the sidebar/left region has content.
UB-HEALTH-EMPTY-TEXT-SIGNALsystemPage health: empty-state text was detected (e.g. "no results", "nothing here").
UB-HEALTH-ERROR-TEXT-SIGNALsystemPage health: error-indicating text was detected on the page.
UB-HEALTH-LOADING-CLASS-SIGNALsystemPage health: a loading/skeleton/spinner CSS class was detected on a visible element.
UB-HEALTH-LOADING-TEXT-SIGNALsystemPage health: loading-indicating text was detected, suggesting the page is not settled.
UB-HEALTH-LOW-ELEMENT-DIVERSITYsystemPage health: all visible elements are navigation-type, suggesting the content body is missing.
UB-HEALTH-LOW-SPATIAL-COVERAGEsystemPage health: rendered elements occupy a critically/abnormally small fraction of the viewport.
UB-HEALTH-MANY-DISABLED-INTERACTIVEsystemPage health: over half of interactive elements are disabled.
UB-HEALTH-NO-CONTENT-ELEMENTSsystemPage health: no elements were found in the content region.
UB-HEALTH-OFF-SCREEN-ELEMENTsystemPage health: a visible element is positioned entirely off-screen.
UB-HEALTH-SPARSE-CONTENTsystemPage health: the content region has very few elements.
UB-HEALTH-ZERO-SIZE-ELEMENTsystemPage health: a visible element has zero width or height.
UB-LOW-CONFIDENCEelementThe best matching element has confidence below the acceptance threshold.
UB-MULTIPLE-ELEMENTSelementMultiple elements match the description; the target is ambiguous.
UB-NAVIGATION-ERRORnetworkNavigation to the target page failed.
UB-NET-ERRORnetworkA network error occurred while performing the action or loading data.
UB-OBS-APP-UNREACHABLEnetworkThe bridge, the runner, or the app behind it could not be reached, so nothing was attempted or observed (an observation carrying it is unknown, not empty).
UB-OBS-BELOW-CONFIDENCE-FLOORsystemObservation unknown: observations existed but every one fell under the caller's confidence floor.
UB-OBS-CAPABILITY-UNAVAILABLE-IN-BUILDsystemObservation unknown: the running build does not serve the capability this observation needs.
UB-OBS-INPUT-MISSINGsystemObservation unknown: a required input was absent (no elements array, no geometry/bbox, no frame, no viewport). This says nothing about the page itself.
UB-OBS-MODEL-REPLY-UNPARSEABLEsystemObservation unknown: a model reply could not be parsed or failed strict validation.
UB-OBS-NEEDS-MULTI-FRAME-INPUTsystemObservation unknown: the question needs more than one frame (e.g. animation_settled) and only one was available.
UB-OBS-PRODUCER-FAILEDsystemThe producer (an observation producer, or the runner answering a proxied call) ran and failed: transport error, HTTP error status, timeout, or a thrown exception. An observation carrying it is unknown — nothing is known about the page.
UB-OBS-PRODUCER-NOT-RUNsystemObservation unknown: the producer did not run because there was nothing to look at yet (e.g. zero registered elements and components).
UB-OBS-STALE-INPUTsystemObservation unknown: the input is known to be stale, so an answer over it would describe a page that no longer exists.
UB-PAGE-LOAD-ERRORnetworkThe page failed to load correctly.
UB-PARSE-ERRORsystemCould not parse the natural language instruction.
UB-STALE-ELEMENTelementThe element reference is no longer attached to the DOM.
UB-STATE-NOT-REACHEDsystemThe expected post-action state was not reached.
UB-UNEXPECTED-STATEsystemThe element or page is in an unexpected state.
UB-UNKNOWN-ERRORsystemAn unknown or uncategorized error occurred.
UB-UNSUPPORTED-ACTIONactionThe requested action type is not supported for this element or surface.
UB-VALIDATION-ERRORsystemThe parsed action failed validation.
UB-VLM-STRUCTURED-PARSE-FAILsystemThe VLM Describe response could not be parsed into the closed structured schema; the prose-only response was returned with structured=null.

The twelve UB-HEALTH-* codes are emitted by the page-health diagnostic (POST /control/page-health) rather than by an action, but they share the same vocabulary and the same recovery machinery.

The nine UB-OBS-* codes are the diagnostic names of the observation envelope's unknown.code values (input_missing → UB-OBS-INPUT-MISSING, and so on — UNKNOWN_CODE_DIAGNOSTICS in @qontinui/ui-bridge/diagnostics is the map). An observation that could not look answers status: "unknown" with one of those codes rather than an empty result. UB-CAPABILITY-UNAVAILABLE is what a runner-direct route (/vision/*) answers on a bridge that has no runner: the build does not serve it, which is not the same fact as an action a guard rejected.

To iterate the vocabulary or look a code up at runtime:

import { UI_BRIDGE_ERROR_CODES, DIAGNOSTICS } from '@qontinui/ui-bridge/diagnostics';

for (const code of UI_BRIDGE_ERROR_CODES) {
const entry = DIAGNOSTICS[code];
console.log(code, entry.category, entry.description, entry.commonCauses);
}

DIAGNOSTICS is also re-exported from the package root; the array and the DiagnosticEntry / DiagnosticCategory types are on the /diagnostics subpath only.

Reading a Failure​

Actions do not throw a typed error class. There is no UIBridgeError. Failure is reported in the response value, which keeps a failed action distinguishable from a genuine exception:

const result = await executor.execute({ instruction: 'click the checkout button' });

if (!result.success) {
result.errorCode; // 'UB-ELEM-NOT-FOUND'
result.error; // human-readable message
result.suggestions; // string[] — legacy flat form
result.alternatives; // SearchResult[] — candidates that scored too low
result.failureInfo; // the structured form, below
}

failureInfo is a StructuredFailureInfo:

interface StructuredFailureInfo {
errorCode: string;
message: string;
elementId?: string;
selectorsTried?: string[];
partialMatches?: PartialMatchInfo[];
elementState?: ElementState;
screenshotContext?: string; // a capture id/path, not an inline image
suggestedActions?: RecoverySuggestion[];
retryRecommended: boolean;
context?: Record<string, unknown>;
durationMs?: number;
timeoutMs?: number;
}

retryRecommended is derived from the catalog — it is true when any recovery template entry for that code is retryable — so an agent can decide to retry without a hand-maintained table of which codes are transient.

Recovery Suggestions​

Every code carries a ranked recovery template. getRecoverySuggestions renders it, substituting ${…} placeholders from a context map:

import { getRecoverySuggestions } from '@qontinui/ui-bridge';

const suggestions = getRecoverySuggestions('UB-ACTION-TIMEOUT', {
waitDurationMs: 5000,
waitCondition: 'results table visible',
});

for (const s of suggestions) {
console.log(s.priority, s.suggestion, s.command, s.confidence, s.retryable);
}
// 1 'Increase the timeout duration (the wait gave up after 5000ms)' … 0.8 true
// 2 "Check if the condition 'results table visible' can ever be met" … 0.7 false
// 3 'Verify the page is responding' 'check page status' 0.6 true

A RecoverySuggestion is { suggestion, command?, confidence, retryable, priority? } — one shape across every failure surface. Unknown placeholders are left verbatim rather than rendered as undefined.

ERROR_SUGGESTIONS is the same catalog pre-rendered without a context map, keyed by code.

Building Error Context​

When you produce a failure yourself, build the context rather than assembling a message by hand. createErrorContext inspects the available elements, detects possible blockers (modals, overlays), counts what was visible and enhances the suggestion list with what it found:

import { createErrorContext, formatErrorContext } from '@qontinui/ui-bridge';

const context = createErrorContext(
'UB-ELEM-NOT-FOUND',
'click the checkout button',
availableElements,
searchCriteria,
nearestMatch
);

console.log(context.pageContext.possibleBlockers); // e.g. ['modal: Confirm delete']
console.log(formatErrorContext(context)); // LLM-ready prose rendering

An AIErrorContext carries code, message, attemptedAction, searchCriteria?, searchResults (candidate count plus the nearest match and why it was rejected), pageContext (url, title, visible element count, possible blockers), suggestions and an optional stack.

For the minimal case — a code and a message with no element context — use createSimpleError:

import { createSimpleError } from '@qontinui/ui-bridge';

const err = createSimpleError('UB-UNSUPPORTED-ACTION');
// { code: 'UB-UNSUPPORTED-ACTION', message: '…' }

Two more helpers make triage cheap:

import { isRecoverableError, getBestRecoverySuggestion } from '@qontinui/ui-bridge';

isRecoverableError('UB-PAGE-LOAD-ERROR'); // false
getBestRecoverySuggestion(context); // highest-confidence suggestion, or null

Automated Recovery​

POST /ai/recovery/attempt takes a structured failure and retries it under the catalog's strategies:

POST /ai/recovery/attempt
Content-Type: application/json

{
"failure": { "errorCode": "UB-ELEM-NOT-VISIBLE", "message": "…", "retryRecommended": true },
"instruction": "click the checkout button",
"elementId": "checkout-btn",
"maxRetries": 3
}

The response is a RecoveryAttemptResult: { recovered, strategiesAttempted, finalResult?, error?, durationMs } — strategiesAttempted names what was tried, so a failed recovery is still diagnostic rather than opaque.

Server-Side Codes​

Handlers built with @qontinui/ui-bridge-server map their internal error strings onto the same vocabulary via mapInternalErrorCode, and buildActionFailureDetails assembles the failureDetails field carried on component-action and workflow-step results:

import { buildActionFailureDetails } from '@qontinui/ui-bridge/diagnostics';

const details = buildActionFailureDetails('UB-ELEM-NOT-VISIBLE', 'Submit is off-screen', {
elementId: 'submit-btn',
visibilityReason: 'off-screen',
durationMs: 120,
});
// details.suggestedActions and details.retryRecommended come from the catalog

This is why an HTTP error body and an in-process failure agree on the code: both render from the generated catalog, and neither hand-maintains a table.