Error reference
Every failure is a closed-set code with structured details. Branch on the code, never regex the message.
Every failure prints a human line plus a machine block on stderr, and exits 1:
{ "relayError": {
"code": "stale_ref",
"message": "chrome_click_element: @e19 is not a known ref (...). Re-run `chrome-relay snapshot` and use a fresh ref.",
"tool": "chrome_click_element",
"phase": "resolve_ref",
"details": { "ref": "e19" },
"retryable": false } }
An agent that gets a string error has to regex-match prose to decide what to do next. A code is mechanical. The set is closed — these are all of them:
The codes, and what to do about each
| Code | Means | Agent's move |
|---|---|---|
stale_ref |
The @ref's element is gone — navigation killed it, or the node vanished and no same-role/name replacement exists | Re-run snapshot, use a fresh ref. Never retry the old one |
click_intercepted |
The ref resolved, but an overlay / sticky header / modal owns the click point. The click was NOT delivered | Read details.interceptor, dismiss it (close the modal, scroll), retry |
element_not_found |
CSS selector matched nothing, or matched a zero-size/unfocusable element | Check the selector against a fresh snapshot; or switch to refs |
target_conflict |
Two targeting modes at once (--tab + --workspace, or a ref + --tab pointing elsewhere) |
Drop one. Refs need no target at all |
target_not_found |
The tab/workspace/group doesn't exist (closed, typo) | chrome-relay tabs to re-orient |
invalid_arguments |
Malformed input: bad CSS syntax, x-without-y, wrong types | Fix the call — the message names the field |
timeout |
The operation outran its deadline | Usually the page is slow — check console/network, then retry once |
cdp_error |
Chrome's debugger protocol refused (e.g. attaching to a chrome:// page) |
Some surfaces are off-limits to CDP; work in a normal tab |
chrome_api_error |
A chrome.* extension API failed |
Read the message; often a transient tab state |
extension_not_connected |
The extension isn't installed/connected to the host | chrome-relay doctor, installation |
native_host_disconnected |
The bridge process died mid-call | Retry once — Chrome respawns the host on demand |
external_dependency_missing |
A system tool the command needs is absent (e.g. ffmpeg for --gif) |
Install it, or pass the documented skip flag |
partial_success_disallowed |
A multi-target operation could only partially apply, and you didn't opt into that | Pass --allow-partial if partial is acceptable; otherwise fix the failing target |
unsupported_tool |
The extension doesn't know this tool — it's older than the CLI | Update the extension (chrome://extensions → Update) |
internal_error |
A bug — something threw outside the closed set | Worth reporting. By design this should be unreachable from normal agent mistakes |
Notices (non-fatal, stderr)
| Notice | Means |
|---|---|
deprecated |
You used read/ax/click-ax — they work, but switch to snapshot/refs |
target_overridden |
A subcommand-level target overrode a program-level one |
cli_outdated / extension_outdated |
Version skew across the bridge — run chrome-relay update or update the extension |
The design rule behind this
No silent partial success, no auto-fallbacks, no "click failed" without saying which of the five things that could mean. The error is the diagnosis: stale_ref tells you the page moved on, click_intercepted hands you the interceptor's tag and node id, invalid_arguments names the field. An agent transcript containing the error should already contain the fix.