# Error reference

Every error code BrowserHive can produce (98 codes), generated from `ERROR_REGISTRY` in `@browserhive/contracts/errors`. Each code has a stable anchor: `errors.md#<CODE>`, which is also the `type` URL of HTTP problem responses (`https://browserhive.ai/docs/errors#<CODE>`).

## How errors reach you

- **MCP tools:** the result has `isError: true` and a text block `[CODE] message` (a stable text format clients may parse). The structured form `{ code, message, retryable, hint?, details? }` is in `_meta["browserhive.ai/error"]`.
- **REST API:** `application/problem+json` (RFC 9457) with `type`, `title`, `status`, `detail`, plus `code`, `retryable`, `hint`, `details` and `request_id`.
- **WebSocket:** a frame with `kind: "error"` and payload `{ code, title, hint?, details?, request_id? }`; the `corr` of the failed command is echoed.
- **CLI:** boot errors print `browserhive: [CODE] message` to stderr and exit with the code listed per entry.

## Retry guidance

| `retryable` | Meaning |
|---|---|
| `never` | do not retry; the request cannot succeed as sent |
| `immediate` | safe to retry right away |
| `backoff` | retry later with backoff |
| `after_operator` | retry after an operator acts (unlock the vault, resolve a request, change policy) |
| `different_args` | retry only with different arguments |

## Domain errors

Returned by tools and the REST API when a request cannot be served (unknown session, blocked URL, vault locked…).

| Code | Title | HTTP | Retryable |
|---|---|---|---|
| [`SESSION_NOT_FOUND`](#SESSION_NOT_FOUND) | Session not found | 404 | different_args |
| [`SESSION_ALREADY_EXISTS`](#SESSION_ALREADY_EXISTS) | Session id collision | 409 | immediate |
| [`SESSION_DEAD`](#SESSION_DEAD) | Session is dead | 410 | different_args |
| [`SESSION_LIMIT_REACHED`](#SESSION_LIMIT_REACHED) | Session limit reached | 429 | backoff |
| [`SESSION_ACCESS_DENIED`](#SESSION_ACCESS_DENIED) | Session not found | 404 | never |
| [`SESSION_NOT_AVAILABLE`](#SESSION_NOT_AVAILABLE) | Session not available | 409 | after_operator |
| [`SESSION_NOT_LIVE`](#SESSION_NOT_LIVE) | Session is not live | 409 | never |
| [`SESSION_LIVE`](#SESSION_LIVE) | Session is still live | 409 | never |
| [`UNKNOWN_CHANNEL`](#UNKNOWN_CHANNEL) | Unknown browser channel | 400 | different_args |
| [`INVALID_SLUG`](#INVALID_SLUG) | Invalid slug | 400 | different_args |
| [`UNSAFE_LAUNCH_ARG`](#UNSAFE_LAUNCH_ARG) | Launch argument denied | 400 | different_args |
| [`INVALID_PERSISTENCE_CONFIG`](#INVALID_PERSISTENCE_CONFIG) | Invalid persistence config | 400 | different_args |
| [`TAB_NOT_FOUND`](#TAB_NOT_FOUND) | Tab not found | 404 | different_args |
| [`PATH_NOT_ALLOWED`](#PATH_NOT_ALLOWED) | Path outside the sandbox | 400 | different_args |
| [`AUTH_STATE_NOT_FOUND`](#AUTH_STATE_NOT_FOUND) | Saved auth state not found | 404 | different_args |
| [`EVALUATE_DISABLED`](#EVALUATE_DISABLED) | evaluate is disabled | 403 | never |
| [`ELEMENT_NOT_ACTIONABLE`](#ELEMENT_NOT_ACTIONABLE) | Element not actionable | 422 | backoff |
| [`ELEMENT_NOT_FOUND`](#ELEMENT_NOT_FOUND) | Element not found | 404 | different_args |
| [`NAVIGATION_TIMEOUT`](#NAVIGATION_TIMEOUT) | Navigation timed out | 504 | backoff |
| [`NAVIGATION_FAILED`](#NAVIGATION_FAILED) | Navigation failed | 502 | backoff |
| [`WAIT_TIMEOUT`](#WAIT_TIMEOUT) | Wait timed out | 504 | backoff |
| [`SCRIPT_ERROR`](#SCRIPT_ERROR) | Script threw | 422 | different_args |
| [`DOWNLOAD_FAILED`](#DOWNLOAD_FAILED) | Download failed | 502 | backoff |
| [`UPLOAD_FAILED`](#UPLOAD_FAILED) | Upload failed | 422 | different_args |
| [`PAGE_CLOSED`](#PAGE_CLOSED) | Page closed | 410 | different_args |
| [`BROWSER_CRASHED`](#BROWSER_CRASHED) | Browser crashed | 500 | after_operator |
| [`URL_BLOCKED`](#URL_BLOCKED) | URL blocked by policy | 403 | never |
| [`BROWSER_NOT_INSTALLED`](#BROWSER_NOT_INSTALLED) | Browser not installed | 503 | after_operator |
| [`VAULT_NOT_CONFIGURED`](#VAULT_NOT_CONFIGURED) | No vault configured | 404 | never |
| [`VAULT_LOCKED`](#VAULT_LOCKED) | Vault locked | 409 | after_operator |
| [`VAULT_ENTRY_NOT_FOUND`](#VAULT_ENTRY_NOT_FOUND) | Vault entry not found | 404 | different_args |
| [`VAULT_NOT_AUTHORIZED`](#VAULT_NOT_AUTHORIZED) | Vault entry not authorized | 403 | never |
| [`EVALUATE_REQUIRED_OFF`](#EVALUATE_REQUIRED_OFF) | Entry requires evaluate disabled | 403 | never |
| [`DASHBOARD_DENIED`](#DASHBOARD_DENIED) | Fill denied by operator | 403 | after_operator |
| [`VAULT_UNLOCK_FAILED`](#VAULT_UNLOCK_FAILED) | Vault unlock failed | 401 | different_args |
| [`VAULT_SYNC_UNSUPPORTED`](#VAULT_SYNC_UNSUPPORTED) | Vault sync unsupported | 400 | never |
| [`VAULT_BACKEND_ERROR`](#VAULT_BACKEND_ERROR) | Vault backend error | 502 | backoff |
| [`ATTENTION_REQUIRES_HTTP`](#ATTENTION_REQUIRES_HTTP) | Attention requires http | 400 | never |
| [`ATTENTION_NOT_OPEN`](#ATTENTION_NOT_OPEN) | Attention request is not open | 409 | never |
| [`CONFIRM_NOT_OPEN`](#CONFIRM_NOT_OPEN) | Confirmation is not open | 409 | never |
| [`INPUT_NOT_PERMITTED`](#INPUT_NOT_PERMITTED) | Input not permitted | 409 | after_operator |
| [`SCREENCAST_FAILED`](#SCREENCAST_FAILED) | Screencast failed | 502 | backoff |
| [`TOOL_NOT_AVAILABLE`](#TOOL_NOT_AVAILABLE) | Tool not available | 400 | never |
| [`INVALID_ARGUMENTS`](#INVALID_ARGUMENTS) | Invalid arguments | 400 | different_args |
| [`TRACE_UNAVAILABLE`](#TRACE_UNAVAILABLE) | Trace unavailable | 404 | never |
| [`SCREENSHOT_UNAVAILABLE`](#SCREENSHOT_UNAVAILABLE) | Screenshot unavailable | 404 | never |
| [`INTERNAL_ERROR`](#INTERNAL_ERROR) | Internal error | 500 | backoff |

<a id="SESSION_NOT_FOUND"></a>
### `SESSION_NOT_FOUND`

| Property | Value |
|---|---|
| Title | Session not found |
| HTTP status | 404 |
| Category | `domain` |
| Retryable | `different_args` (retry only with different arguments) |

Message: `No browser session with id '{session_id}'`

Hint: Use list_sessions to see live sessions.

Cause: The session id is unknown, mistyped, or the session was already closed and removed.

Resolution: Call list_sessions and use one of the returned ids, or launch a new session.

Details:

| Field | Type | Required | Constraints |
|---|---|---|---|
| `session_id` | `string` | yes | — |

<a id="SESSION_ALREADY_EXISTS"></a>
### `SESSION_ALREADY_EXISTS`

| Property | Value |
|---|---|
| Title | Session id collision |
| HTTP status | 409 |
| Category | `domain` |
| Retryable | `immediate` (safe to retry right away) |

Message: `Session '{session_id}' already exists. Slug+nanoid collision is exceptionally rare; the caller should retry.`

Hint: Retry launch_session; a fresh id is generated on every call.

Cause: The generated `<slug>-<nanoid8>` id collided with a live session (36^8 space).

Resolution: Retry immediately; a collision twice in a row indicates a broken id generator.

Details:

| Field | Type | Required | Constraints |
|---|---|---|---|
| `session_id` | `string` | yes | — |

<a id="SESSION_DEAD"></a>
### `SESSION_DEAD`

| Property | Value |
|---|---|
| Title | Session is dead |
| HTTP status | 410 |
| Category | `domain` |
| Retryable | `different_args` (retry only with different arguments) |

Message: `Session '{session_id}' is dead — its underlying browser process crashed.`

Hint: Close the session and launch a new one.

Cause: The browser context or process disconnected; the session is awaiting reaping.

Resolution: Launch a replacement session; the crashed one is closed with reason `crash`.

Details:

| Field | Type | Required | Constraints |
|---|---|---|---|
| `session_id` | `string` | yes | — |

<a id="SESSION_LIMIT_REACHED"></a>
### `SESSION_LIMIT_REACHED`

| Property | Value |
|---|---|
| Title | Session limit reached |
| HTTP status | 429 |
| Category | `domain` |
| Retryable | `backoff` (retry later with backoff) |

Message: `Concurrent session limit reached (max={limit}). Close a session and retry.`

Hint: Close an idle session or wait for a lease to expire, then retry.

Cause: The number of live sessions equals `maxSessions` (derived from host RAM by default).

Resolution: Close sessions you no longer need, or raise `maxSessions` on a host with more memory.

Details:

| Field | Type | Required | Constraints |
|---|---|---|---|
| `limit` | `integer` | yes | — |
| `live` | `integer` | yes | — |

<a id="SESSION_ACCESS_DENIED"></a>
### `SESSION_ACCESS_DENIED`

| Property | Value |
|---|---|
| Title | Session not found |
| HTTP status | 404 |
| Category | `domain` |
| Retryable | `never` (do not retry; the request cannot succeed as sent) |

Message: `No browser session with id '{session_id}'`

Hint: Use list_sessions to see the sessions you own.

Cause: The session belongs to a different principal and ownership enforcement is on (`auth=token`).

Resolution: Only the owning principal may use a session; launch your own.

Details:

| Field | Type | Required | Constraints |
|---|---|---|---|
| `session_id` | `string` | yes | — |

<a id="SESSION_NOT_AVAILABLE"></a>
### `SESSION_NOT_AVAILABLE`

| Property | Value |
|---|---|
| Title | Session not available |
| HTTP status | 409 |
| Category | `domain` |
| Retryable | `after_operator` (retry after an operator acts (unlock the vault, resolve a request, change policy)) |

Message: `Session '{session_id}' is not available (state: {state}).`

Hint: Wait until the session is live, or pick another session.

Cause: The session is launching, draining, paused for an operator, closed or crashed.

Resolution: Retry once the session state returns to `live`.

Details:

| Field | Type | Required | Constraints |
|---|---|---|---|
| `session_id` | `string` | yes | — |
| `state` | `string` | yes | — |

<a id="SESSION_NOT_LIVE"></a>
### `SESSION_NOT_LIVE`

| Property | Value |
|---|---|
| Title | Session is not live |
| HTTP status | 409 |
| Category | `domain` |
| Retryable | `never` (do not retry; the request cannot succeed as sent) |

Message: `Session '{session_id}' is not live.`

Hint: This action needs a live session; the session has ended.

Cause: An operator action that needs a running browser (live view, input, terminate) targeted a closed session.

Resolution: Open the trace or history views instead.

Details:

| Field | Type | Required | Constraints |
|---|---|---|---|
| `session_id` | `string` | yes | — |

<a id="SESSION_LIVE"></a>
### `SESSION_LIVE`

| Property | Value |
|---|---|
| Title | Session is still live |
| HTTP status | 409 |
| Category | `domain` |
| Retryable | `never` (do not retry; the request cannot succeed as sent) |

Message: `Session '{session_id}' is still live.`

Hint: Terminate or close the session first.

Cause: An action that needs a finished session (archive, delete, trace download) targeted a live one.

Resolution: Close the session, then retry.

Details:

| Field | Type | Required | Constraints |
|---|---|---|---|
| `session_id` | `string` | yes | — |

<a id="UNKNOWN_CHANNEL"></a>
### `UNKNOWN_CHANNEL`

| Property | Value |
|---|---|
| Title | Unknown browser channel |
| HTTP status | 400 |
| Category | `domain` |
| Retryable | `different_args` (retry only with different arguments) |

Message: `Unknown browser channel '{channel}'. Expected one of: chromium, chrome, edge.`

Hint: Pass channel as one of chromium, chrome or edge.

Cause: The `channel` argument is not a supported Chromium-family channel.

Resolution: Use `chromium` (bundled), or `chrome`/`edge` when that browser is installed on the host.

Details:

| Field | Type | Required | Constraints |
|---|---|---|---|
| `channel` | `string` | yes | — |
| `supported` | `string[]` | yes | — |

<a id="INVALID_SLUG"></a>
### `INVALID_SLUG`

| Property | Value |
|---|---|
| Title | Invalid slug |
| HTTP status | 400 |
| Category | `domain` |
| Retryable | `different_args` (retry only with different arguments) |

Message: `Invalid slug '{slug}'. Slugs must match /^[a-z][a-z0-9-]{1,31}$/ (start with a lowercase letter, 2–32 chars, lowercase alphanumerics and dashes).`

Hint: Use a short lowercase name such as "shop" or "docs-crawl".

Cause: The slug does not match the filesystem-safe grammar.

Resolution: Lowercase letters, digits and dashes only; start with a letter; 2–32 characters.

Details:

| Field | Type | Required | Constraints |
|---|---|---|---|
| `slug` | `string` | yes | — |
| `pattern` | `string` | yes | — |

<a id="UNSAFE_LAUNCH_ARG"></a>
### `UNSAFE_LAUNCH_ARG`

| Property | Value |
|---|---|
| Title | Launch argument denied |
| HTTP status | 400 |
| Category | `domain` |
| Retryable | `different_args` (retry only with different arguments) |

Message: `Launch arg '{arg}' is on the deny-list and would break session isolation.`

Hint: Remove the argument from launch_options.args.

Cause: A Chromium flag that changes the profile directory, sandbox or debugging surface was passed.

Resolution: Use the persistence modes and channel options instead of raw isolation-breaking flags.

Details:

| Field | Type | Required | Constraints |
|---|---|---|---|
| `arg` | `string` | yes | — |

<a id="INVALID_PERSISTENCE_CONFIG"></a>
### `INVALID_PERSISTENCE_CONFIG`

| Property | Value |
|---|---|
| Title | Invalid persistence config |
| HTTP status | 400 |
| Category | `domain` |
| Retryable | `different_args` (retry only with different arguments) |

Message: `Invalid persistence config: {reason}`

Hint: Check the persistence_mode / restore_profile / storage_state / incognito combination.

Cause: The requested combination of persistence mode and launch options is contradictory.

Resolution: See the persistence matrix in the tool reference and pick a consistent combination.

Details:

| Field | Type | Required | Constraints |
|---|---|---|---|
| `reason` | `string` | yes | — |

<a id="TAB_NOT_FOUND"></a>
### `TAB_NOT_FOUND`

| Property | Value |
|---|---|
| Title | Tab not found |
| HTTP status | 404 |
| Category | `domain` |
| Retryable | `different_args` (retry only with different arguments) |

Message: `Tab '{tab_id}' not found in session '{session_id}'.`

Hint: Use list_tabs to see open tabs.

Cause: The tab id is unknown or the tab was closed; `<active>` means the session has no open tab.

Resolution: Call list_tabs, or open a tab with new_tab.

Details:

| Field | Type | Required | Constraints |
|---|---|---|---|
| `session_id` | `string` | yes | — |
| `tab_id` | `string` | yes | — |

<a id="PATH_NOT_ALLOWED"></a>
### `PATH_NOT_ALLOWED`

| Property | Value |
|---|---|
| Title | Path outside the sandbox |
| HTTP status | 400 |
| Category | `domain` |
| Retryable | `different_args` (retry only with different arguments) |

Message: `Path '{path}' is outside the allowed sandbox roots.`

Hint: Use a relative path or an absolute path under an allowed root.

Cause: The resolved path (symlinks followed) escapes the session directory and the uploads directory.

Resolution: Write inside the session directory (relative paths resolve there) or the uploads directory.

Details:

| Field | Type | Required | Constraints |
|---|---|---|---|
| `path` | `string` | yes | — |
| `roots` | `string[]` | yes | — |

<a id="AUTH_STATE_NOT_FOUND"></a>
### `AUTH_STATE_NOT_FOUND`

| Property | Value |
|---|---|
| Title | Saved auth state not found |
| HTTP status | 404 |
| Category | `domain` |
| Retryable | `different_args` (retry only with different arguments) |

Message: `No saved {kind_label} snapshot named '{name}'. Use list_saved_auths to see what is available.`

Hint: Call list_saved_auths and use one of the returned names.

Cause: No snapshot of the requested kind exists under that name in the auth-states directory.

Resolution: Save one with save_storage_state or save_full_profile first.

Details:

| Field | Type | Required | Constraints |
|---|---|---|---|
| `name` | `string` | yes | — |
| `kind` | one of `storage`, `profile` | yes | — |

<a id="EVALUATE_DISABLED"></a>
### `EVALUATE_DISABLED`

| Property | Value |
|---|---|
| Title | evaluate is disabled |
| HTTP status | 403 |
| Category | `domain` |
| Retryable | `never` (do not retry; the request cannot succeed as sent) |

Message: `The 'evaluate' tool is disabled for session '{session_id}'.`

Hint: Use the structured tools (click, fill, get_content, snapshot) instead.

Cause: The session was launched with `disable_evaluate: true`, or the server runs with `allowEvaluate=false`.

Resolution: Relaunch without `disable_evaluate`, or ask the operator to enable `allowEvaluate`.

Details:

| Field | Type | Required | Constraints |
|---|---|---|---|
| `session_id` | `string` | yes | — |
| `scope` | one of `session`, `server` | yes | — |

<a id="ELEMENT_NOT_ACTIONABLE"></a>
### `ELEMENT_NOT_ACTIONABLE`

| Property | Value |
|---|---|
| Title | Element not actionable |
| HTTP status | 422 |
| Category | `domain` |
| Retryable | `backoff` (retry later with backoff) |

Message: `Element '{selector}' did not become actionable within the timeout — it may be hidden, detached, disabled, covered by another element, or the selector matched the wrong element. Use a more specific, visible selector, or raise the timeout.`

Hint: Take a snapshot to find a visible, unique selector; raise timeout_ms if the page is slow.

Cause: The target was hidden, detached, disabled, covered, or the selector matched the wrong element.

Resolution: Use a more specific selector or wait for the element to become visible first.

Details:

| Field | Type | Required | Constraints |
|---|---|---|---|
| `selector` | `string` | yes | — |
| `detail` | `string` | no | — |

<a id="ELEMENT_NOT_FOUND"></a>
### `ELEMENT_NOT_FOUND`

| Property | Value |
|---|---|
| Title | Element not found |
| HTTP status | 404 |
| Category | `domain` |
| Retryable | `different_args` (retry only with different arguments) |

Message: `No element matched selector '{selector}'.`

Hint: Take a snapshot and pick a selector that matches exactly one element.

Cause: The selector matched nothing, or matched several elements under strict mode.

Resolution: Refine the selector; `count` in details tells you how many matched.

Details:

| Field | Type | Required | Constraints |
|---|---|---|---|
| `selector` | `string` | yes | — |
| `count` | `integer` | no | — |

<a id="NAVIGATION_TIMEOUT"></a>
### `NAVIGATION_TIMEOUT`

| Property | Value |
|---|---|
| Title | Navigation timed out |
| HTTP status | 504 |
| Category | `domain` |
| Retryable | `backoff` (retry later with backoff) |

Message: `Navigation to '{url}' did not finish within {timeout_ms} ms.`

Hint: Retry with a longer timeout_ms or a lighter wait_until (commit, domcontentloaded).

Cause: The page did not reach the requested load state before the timeout.

Resolution: Raise the timeout, choose an earlier `wait_until`, or check the network.

Details:

| Field | Type | Required | Constraints |
|---|---|---|---|
| `url` | `string` | yes | — |
| `timeout_ms` | `integer` | yes | — |

<a id="NAVIGATION_FAILED"></a>
### `NAVIGATION_FAILED`

| Property | Value |
|---|---|
| Title | Navigation failed |
| HTTP status | 502 |
| Category | `domain` |
| Retryable | `backoff` (retry later with backoff) |

Message: `Navigation to '{url}' failed ({net_error}).`

Hint: Check the URL and network; net_error names the Chromium failure.

Cause: Chromium reported a network error (`net::ERR_*`): DNS, TLS, connection refused, and so on.

Resolution: Verify the URL resolves and is reachable from the host; retry transient failures.

Details:

| Field | Type | Required | Constraints |
|---|---|---|---|
| `url` | `string` | yes | — |
| `net_error` | `string` | yes | — |

<a id="WAIT_TIMEOUT"></a>
### `WAIT_TIMEOUT`

| Property | Value |
|---|---|
| Title | Wait timed out |
| HTTP status | 504 |
| Category | `domain` |
| Retryable | `backoff` (retry later with backoff) |

Message: `Timed out after {timeout_ms} ms waiting for {what}.`

Hint: Raise timeout_ms or wait for a different condition.

Cause: The awaited selector state, URL or load state did not occur in time.

Resolution: Check the page state with snapshot, then wait for a condition that will happen.

Details:

| Field | Type | Required | Constraints |
|---|---|---|---|
| `what` | `string` | yes | — |
| `timeout_ms` | `integer` | yes | — |

<a id="SCRIPT_ERROR"></a>
### `SCRIPT_ERROR`

| Property | Value |
|---|---|
| Title | Script threw |
| HTTP status | 422 |
| Category | `domain` |
| Retryable | `different_args` (retry only with different arguments) |

Message: `The evaluated script threw: {message}`

Hint: Fix the script; the first line of the page error is in details.message.

Cause: The expression passed to `evaluate` threw inside the page.

Resolution: Correct the script or guard against missing globals.

Details:

| Field | Type | Required | Constraints |
|---|---|---|---|
| `message` | `string` | yes | — |

<a id="DOWNLOAD_FAILED"></a>
### `DOWNLOAD_FAILED`

| Property | Value |
|---|---|
| Title | Download failed |
| HTTP status | 502 |
| Category | `domain` |
| Retryable | `backoff` (retry later with backoff) |

Message: `Download failed: {reason}`

Hint: Retry; if it persists, check that the link starts a download.

Cause: The browser reported the download as failed or cancelled.

Resolution: Retry the download; verify the target actually serves a file.

Details:

| Field | Type | Required | Constraints |
|---|---|---|---|
| `reason` | `string` | yes | — |

<a id="UPLOAD_FAILED"></a>
### `UPLOAD_FAILED`

| Property | Value |
|---|---|
| Title | Upload failed |
| HTTP status | 422 |
| Category | `domain` |
| Retryable | `different_args` (retry only with different arguments) |

Message: `Upload failed: {reason}`

Hint: Check that the selector targets a file input and the file exists under uploads/.

Cause: `setInputFiles` failed: wrong element, missing file, or a page that rejected the input.

Resolution: Target an `<input type=file>` and place the file in the uploads directory.

Details:

| Field | Type | Required | Constraints |
|---|---|---|---|
| `reason` | `string` | yes | — |

<a id="PAGE_CLOSED"></a>
### `PAGE_CLOSED`

| Property | Value |
|---|---|
| Title | Page closed |
| HTTP status | 410 |
| Category | `domain` |
| Retryable | `different_args` (retry only with different arguments) |

Message: `Tab '{tab_id}' was closed while the action was running.`

Hint: Use list_tabs and target an open tab.

Cause: The page or its execution context was destroyed mid-call (closed tab, navigation away).

Resolution: Re-open or re-target the tab and retry.

Details:

| Field | Type | Required | Constraints |
|---|---|---|---|
| `tab_id` | `string` | yes | — |

<a id="BROWSER_CRASHED"></a>
### `BROWSER_CRASHED`

| Property | Value |
|---|---|
| Title | Browser crashed |
| HTTP status | 500 |
| Category | `domain` |
| Retryable | `after_operator` (retry after an operator acts (unlock the vault, resolve a request, change policy)) |

Message: `The browser behind session '{session_id}' crashed.`

Hint: Launch a new session; the crashed one will be reaped.

Cause: The browser context or process was closed unexpectedly.

Resolution: Launch a replacement session; check host memory if it repeats.

Details:

| Field | Type | Required | Constraints |
|---|---|---|---|
| `session_id` | `string` | yes | — |

<a id="URL_BLOCKED"></a>
### `URL_BLOCKED`

| Property | Value |
|---|---|
| Title | URL blocked by policy |
| HTTP status | 403 |
| Category | `domain` |
| Retryable | `never` (do not retry; the request cannot succeed as sent) |

Message: `The URL '{url}' is blocked by the administrator (matched the blocklist pattern '{pattern}'). This is an operator policy, not a transient failure — do not retry this URL, and do not try to reach it by another route. Report it to the user if the task cannot continue.`

Hint: Do not retry; report the block to the user.

Cause: The URL matched a pattern in the operator blocklist (`blocklist` file).

Resolution: Operators edit the blocklist file (reloaded live) if the block is unintended.

Details:

| Field | Type | Required | Constraints |
|---|---|---|---|
| `url` | `string` | yes | — |
| `pattern` | `string` | yes | — |

<a id="BROWSER_NOT_INSTALLED"></a>
### `BROWSER_NOT_INSTALLED`

| Property | Value |
|---|---|
| Title | Browser not installed |
| HTTP status | 503 |
| Category | `domain` |
| Retryable | `after_operator` (retry after an operator acts (unlock the vault, resolve a request, change policy)) |

Message: `No browser is installed for channel '{channel}'. Run '{install_command}' on the host.`

Hint: Ask the operator to run browserhive init.

Cause: The Chromium binary for the driver/channel is missing (no postinstall download, D-18).

Resolution: Run `browserhive init`, or install the branded browser for `chrome`/`edge`.

Details:

| Field | Type | Required | Constraints |
|---|---|---|---|
| `channel` | `string` | yes | — |
| `install_command` | `string` | yes | — |

<a id="VAULT_NOT_CONFIGURED"></a>
### `VAULT_NOT_CONFIGURED`

| Property | Value |
|---|---|
| Title | No vault configured |
| HTTP status | 404 |
| Category | `domain` |
| Retryable | `never` (do not retry; the request cannot succeed as sent) |

Message: `No vault backend is configured. Start the server with --vault <backend>.`

Hint: Ask the operator to start the server with vault=bitwarden.

Cause: The server runs with `vault=off`.

Resolution: Start with `--vault bitwarden` and unlock it.

Details: none.

<a id="VAULT_LOCKED"></a>
### `VAULT_LOCKED`

| Property | Value |
|---|---|
| Title | Vault locked |
| HTTP status | 409 |
| Category | `domain` |
| Retryable | `after_operator` (retry after an operator acts (unlock the vault, resolve a request, change policy)) |

Message: `Vault backend is locked. Unlock it on the dashboard Vault page.`

Hint: Ask the operator to unlock the vault, then retry.

Cause: The backend session token is absent or expired.

Resolution: Run `bw unlock --raw` and paste the session token on the dashboard Vault page, or restart the server with `BW_SESSION` exported.

Details:

| Field | Type | Required | Constraints |
|---|---|---|---|
| `backend` | `string` | yes | — |

<a id="VAULT_ENTRY_NOT_FOUND"></a>
### `VAULT_ENTRY_NOT_FOUND`

| Property | Value |
|---|---|
| Title | Vault entry not found |
| HTTP status | 404 |
| Category | `domain` |
| Retryable | `different_args` (retry only with different arguments) |

Message: `Vault entry '{entry_name}' is not in the backend.`

Hint: Use vault_list_available to see entries you may fill.

Cause: No binding or backend item matches the requested entry name.

Resolution: Pick a name from vault_list_available, or ask the operator to bind the item.

Details:

| Field | Type | Required | Constraints |
|---|---|---|---|
| `entry_name` | `string` | yes | — |

<a id="VAULT_NOT_AUTHORIZED"></a>
### `VAULT_NOT_AUTHORIZED`

| Property | Value |
|---|---|
| Title | Vault entry not authorized |
| HTTP status | 403 |
| Category | `domain` |
| Retryable | `never` (do not retry; the request cannot succeed as sent) |

Message: `Vault entry '{entry_name}' is not authorized for this session's slug.`

Hint: The operator controls which sessions may use this entry.

Cause: The binding or group policy does not allow this principal and session slug.

Resolution: Operators edit the binding (slug globs, principals) on the Vault page.

Details:

| Field | Type | Required | Constraints |
|---|---|---|---|
| `entry_name` | `string` | yes | — |

<a id="EVALUATE_REQUIRED_OFF"></a>
### `EVALUATE_REQUIRED_OFF`

| Property | Value |
|---|---|
| Title | Entry requires evaluate disabled |
| HTTP status | 403 |
| Category | `domain` |
| Retryable | `never` (do not retry; the request cannot succeed as sent) |

Message: `Vault entry '{entry_name}' requires the session to have evaluate disabled (launch with disable_evaluate: true).`

Hint: Relaunch the session with disable_evaluate: true.

Cause: The binding is marked `require_no_evaluate` and the session can run scripts.

Resolution: Launch with `disable_evaluate: true` before filling this entry.

Details:

| Field | Type | Required | Constraints |
|---|---|---|---|
| `entry_name` | `string` | yes | — |

<a id="DASHBOARD_DENIED"></a>
### `DASHBOARD_DENIED`

| Property | Value |
|---|---|
| Title | Fill denied by operator |
| HTTP status | 403 |
| Category | `domain` |
| Retryable | `after_operator` (retry after an operator acts (unlock the vault, resolve a request, change policy)) |

Message: `Vault fill for '{entry_name}' was denied by the dashboard (or timed out waiting for confirmation).`

Hint: Ask the operator to approve the fill, then retry.

Cause: The operator rejected the confirmation, or nobody answered before the deadline.

Resolution: Retry when an operator is available to confirm.

Details:

| Field | Type | Required | Constraints |
|---|---|---|---|
| `request_id` | `string` | yes | — |
| `entry_name` | `string` | no | — |

<a id="VAULT_UNLOCK_FAILED"></a>
### `VAULT_UNLOCK_FAILED`

| Property | Value |
|---|---|
| Title | Vault unlock failed |
| HTTP status | 401 |
| Category | `domain` |
| Retryable | `different_args` (retry only with different arguments) |

Message: `Vault unlock failed ({mode}).`

Hint: Create a new session token with `bw unlock --raw` and paste it again.

Cause: The backend rejected the session token (expired after `bw lock` or `bw logout`, or from another `bw` login), or the passphrase of a passphrase backend.

Resolution: Run `bw unlock --raw` as the user that runs BrowserHive and paste the new token, or export it as `BW_SESSION` before start.

Details:

| Field | Type | Required | Constraints |
|---|---|---|---|
| `mode` | `string` | yes | — |

<a id="VAULT_SYNC_UNSUPPORTED"></a>
### `VAULT_SYNC_UNSUPPORTED`

| Property | Value |
|---|---|
| Title | Vault sync unsupported |
| HTTP status | 400 |
| Category | `domain` |
| Retryable | `never` (do not retry; the request cannot succeed as sent) |

Message: `Vault backend '{backend}' does not support sync.`

Hint: Nothing to do; this backend has no sync operation.

Cause: The backend capabilities report `sync: false`.

Resolution: Use a backend that supports sync, or skip the call.

Details:

| Field | Type | Required | Constraints |
|---|---|---|---|
| `backend` | `string` | yes | — |

<a id="VAULT_BACKEND_ERROR"></a>
### `VAULT_BACKEND_ERROR`

| Property | Value |
|---|---|
| Title | Vault backend error |
| HTTP status | 502 |
| Category | `domain` |
| Retryable | `backoff` (retry later with backoff) |

Message: `Vault backend '{backend}' failed ({kind}).`

Hint: Retry; if it persists, check the backend CLI on the host.

Cause: The backend CLI is missing, timed out, or exited with an error.

Resolution: Install or repair the backend CLI (`bw`) and retry.

Details:

| Field | Type | Required | Constraints |
|---|---|---|---|
| `backend` | `string` | yes | — |
| `kind` | one of `not_installed`, `timeout`, `exit` | yes | — |

<a id="ATTENTION_REQUIRES_HTTP"></a>
### `ATTENTION_REQUIRES_HTTP`

| Property | Value |
|---|---|
| Title | Attention requires http |
| HTTP status | 400 |
| Category | `domain` |
| Retryable | `never` (do not retry; the request cannot succeed as sent) |

Message: `'{tool}' requires the http transport; human-in-the-loop attention is not available under stdio.`

Hint: Run the server with transport=http and admin=true.

Cause: Attention needs the shared, long-lived HTTP daemon and the dashboard; stdio is single-client.

Resolution: Start BrowserHive with `--admin` (http transport).

Details:

| Field | Type | Required | Constraints |
|---|---|---|---|
| `tool` | `string` | yes | — |

<a id="ATTENTION_NOT_OPEN"></a>
### `ATTENTION_NOT_OPEN`

| Property | Value |
|---|---|
| Title | Attention request is not open |
| HTTP status | 409 |
| Category | `domain` |
| Retryable | `never` (do not retry; the request cannot succeed as sent) |

Message: `Attention request '{request_id}' is not open (status: {status}).`

Hint: Refresh the queue.

Cause: The request was already resolved, rejected, timed out or cancelled.

Resolution: Nothing to do; the outcome is final.

Details:

| Field | Type | Required | Constraints |
|---|---|---|---|
| `request_id` | `string` | yes | — |
| `status` | `string` | yes | — |

<a id="CONFIRM_NOT_OPEN"></a>
### `CONFIRM_NOT_OPEN`

| Property | Value |
|---|---|
| Title | Confirmation is not open |
| HTTP status | 409 |
| Category | `domain` |
| Retryable | `never` (do not retry; the request cannot succeed as sent) |

Message: `Vault confirmation '{request_id}' is not open (status: {status}).`

Hint: Refresh the confirm queue.

Cause: The confirmation was already decided or expired.

Resolution: Nothing to do; the outcome is final.

Details:

| Field | Type | Required | Constraints |
|---|---|---|---|
| `request_id` | `string` | yes | — |
| `status` | `string` | yes | — |

<a id="INPUT_NOT_PERMITTED"></a>
### `INPUT_NOT_PERMITTED`

| Property | Value |
|---|---|
| Title | Input not permitted |
| HTTP status | 409 |
| Category | `domain` |
| Retryable | `after_operator` (retry after an operator acts (unlock the vault, resolve a request, change policy)) |

Message: `Input is not permitted on session '{session_id}': no takeover attention request is open.`

Hint: Input is only accepted while a takeover attention request is open.

Cause: Live-view input is gated on an open `takeover` request from the agent.

Resolution: Wait for the agent to request attention in takeover mode.

Details:

| Field | Type | Required | Constraints |
|---|---|---|---|
| `session_id` | `string` | yes | — |

<a id="SCREENCAST_FAILED"></a>
### `SCREENCAST_FAILED`

| Property | Value |
|---|---|
| Title | Screencast failed |
| HTTP status | 502 |
| Category | `domain` |
| Retryable | `backoff` (retry later with backoff) |

Message: `Screencast for session '{session_id}' failed: {reason}`

Hint: Retry; if the session is closing the stream cannot start.

Cause: The CDP screencast could not be started or the bridge lost its page.

Resolution: Retry the live view; check the session is live.

Details:

| Field | Type | Required | Constraints |
|---|---|---|---|
| `session_id` | `string` | yes | — |
| `reason` | `string` | yes | — |

<a id="TOOL_NOT_AVAILABLE"></a>
### `TOOL_NOT_AVAILABLE`

| Property | Value |
|---|---|
| Title | Tool not available |
| HTTP status | 400 |
| Category | `domain` |
| Retryable | `never` (do not retry; the request cannot succeed as sent) |

Message: `Tool '{tool}' is not available on this server (requires {requires}).`

Hint: The tool needs a server feature that is off.

Cause: The tool pack requires a transport, vault or admin feature that is not enabled.

Resolution: Enable the feature on the server, or avoid the tool.

Details:

| Field | Type | Required | Constraints |
|---|---|---|---|
| `tool` | `string` | yes | — |
| `requires` | `string` | yes | — |

<a id="INVALID_ARGUMENTS"></a>
### `INVALID_ARGUMENTS`

| Property | Value |
|---|---|
| Title | Invalid arguments |
| HTTP status | 400 |
| Category | `domain` |
| Retryable | `different_args` (retry only with different arguments) |

Message: `Invalid arguments: {summary}`

Hint: Fix the listed issues and call the tool again.

Cause: The tool arguments failed schema validation.

Resolution: Each issue names the path and what is wrong.

Details:

| Field | Type | Required | Constraints |
|---|---|---|---|
| `issues` | `object[]` | yes | each item: keys `path`, `message` |

<a id="TRACE_UNAVAILABLE"></a>
### `TRACE_UNAVAILABLE`

| Property | Value |
|---|---|
| Title | Trace unavailable |
| HTTP status | 404 |
| Category | `domain` |
| Retryable | `never` (do not retry; the request cannot succeed as sent) |

Message: `No trace is available for this session.`

Hint: Tracing is on when trace=true (default with admin).

Cause: Tracing was off for the session, or the trace failed to finalize.

Resolution: Enable `trace` and relaunch; see TRACE_START_FAILED warnings.

Details:

| Field | Type | Required | Constraints |
|---|---|---|---|
| `enabled` | `boolean` | yes | — |

<a id="SCREENSHOT_UNAVAILABLE"></a>
### `SCREENSHOT_UNAVAILABLE`

| Property | Value |
|---|---|
| Title | Screenshot unavailable |
| HTTP status | 404 |
| Category | `domain` |
| Retryable | `never` (do not retry; the request cannot succeed as sent) |

Message: `No screenshot is stored for event '{event_id}'.`

Hint: Screenshots are kept per tool call while retention allows.

Cause: The event produced no screenshot, or retention removed it.

Resolution: Nothing to do; the artifact no longer exists.

Details:

| Field | Type | Required | Constraints |
|---|---|---|---|
| `event_id` | `string` | yes | — |

<a id="INTERNAL_ERROR"></a>
### `INTERNAL_ERROR`

| Property | Value |
|---|---|
| Title | Internal error |
| HTTP status | 500 |
| Category | `domain` |
| Retryable | `backoff` (retry later with backoff) |

Message: `Internal error (ref {ref}).`

Hint: Quote the ref to the operator; the logs hold the detail.

Cause: An unexpected failure that no typed code describes.

Resolution: Search the logs for the ref (request id) and file a bug if it repeats.

Details:

| Field | Type | Required | Constraints |
|---|---|---|---|
| `ref` | `string` | yes | — |

## Boot errors

Stop the process before it serves anything. The CLI prints `[CODE] message` and exits with the listed exit code.

| Code | Title | HTTP | Retryable |
|---|---|---|---|
| [`ADMIN_REQUIRES_HTTP`](#ADMIN_REQUIRES_HTTP) | Dashboard requires http | 500 | never |
| [`INSECURE_BIND_REFUSED`](#INSECURE_BIND_REFUSED) | Insecure bind refused | 500 | never |
| [`PORT_IN_USE`](#PORT_IN_USE) | Port in use | 500 | never |
| [`BIND_FAILED`](#BIND_FAILED) | Bind failed | 500 | never |
| [`CONFIG_INVALID`](#CONFIG_INVALID) | Invalid configuration value | 400 | never |
| [`CONFIG_UNKNOWN_KEY`](#CONFIG_UNKNOWN_KEY) | Unknown configuration key | 400 | never |
| [`BLOCKLIST_LOAD_FAILED`](#BLOCKLIST_LOAD_FAILED) | Blocklist could not be loaded | 400 | never |
| [`DATA_DIR_UNWRITABLE`](#DATA_DIR_UNWRITABLE) | Data directory not writable | 500 | never |
| [`DATA_DIR_LOCKED`](#DATA_DIR_LOCKED) | Data directory in use | 409 | never |
| [`DB_OPEN_FAILED`](#DB_OPEN_FAILED) | Database could not be opened | 500 | never |
| [`DB_NEWER_THAN_BINARY`](#DB_NEWER_THAN_BINARY) | Database newer than this version | 500 | never |
| [`MIGRATION_FAILED`](#MIGRATION_FAILED) | Migration failed | 500 | never |
| [`DB_CORRUPT`](#DB_CORRUPT) | Database corrupt | 500 | never |
| [`UNHANDLED`](#UNHANDLED) | Unhandled error | 500 | never |
| [`RETENTION_FAILED`](#RETENTION_FAILED) | Retention sweep failed | 500 | backoff |
| [`STALE_BROWSER_PROCESSES`](#STALE_BROWSER_PROCESSES) | Stale browser processes found | 500 | never |

<a id="ADMIN_REQUIRES_HTTP"></a>
### `ADMIN_REQUIRES_HTTP`

| Property | Value |
|---|---|
| Title | Dashboard requires http |
| HTTP status | 500 |
| Category | `boot` |
| Retryable | `never` (do not retry; the request cannot succeed as sent) |
| Exit code | 3 |

Message: `admin=true requires transport=http. The dashboard is not available under stdio.`

Hint: Drop admin=true, or use transport=http.

Cause: `admin=true` was combined with `transport=stdio`; stdio is single-client and cannot host the dashboard.

Resolution: Run with the http transport (the default) to use the dashboard.

Details: none.

<a id="INSECURE_BIND_REFUSED"></a>
### `INSECURE_BIND_REFUSED`

| Property | Value |
|---|---|
| Title | Insecure bind refused |
| HTTP status | 500 |
| Category | `boot` |
| Retryable | `never` (do not retry; the request cannot succeed as sent) |
| Exit code | 3 |

Message: `Refusing to bind {host} without authentication. Set auth=token, or set allowInsecureBind=true to accept the risk.`

Hint: Set auth=token for LAN exposure.

Cause: A non-loopback `host` was requested without `auth=token` and without `allowInsecureBind=true`.

Resolution: Enable token auth, or acknowledge the risk with `allowInsecureBind=true`.

Details:

| Field | Type | Required | Constraints |
|---|---|---|---|
| `host` | `string` | yes | — |

<a id="PORT_IN_USE"></a>
### `PORT_IN_USE`

| Property | Value |
|---|---|
| Title | Port in use |
| HTTP status | 500 |
| Category | `boot` |
| Retryable | `never` (do not retry; the request cannot succeed as sent) |
| Exit code | 3 |

Message: `Cannot listen on {host}:{port}: the port is already in use ({errno}).`

Hint: Pick another port with --port, or stop the process holding it.

Cause: Another process (often a previous BrowserHive) is bound to the same host and port.

Resolution: Stop the other process or choose a free port.

Details:

| Field | Type | Required | Constraints |
|---|---|---|---|
| `host` | `string` | yes | — |
| `port` | `integer` | yes | — |
| `errno` | `string` | yes | — |

<a id="BIND_FAILED"></a>
### `BIND_FAILED`

| Property | Value |
|---|---|
| Title | Bind failed |
| HTTP status | 500 |
| Category | `boot` |
| Retryable | `never` (do not retry; the request cannot succeed as sent) |
| Exit code | 1 |

Message: `Cannot listen on {host}:{port} ({errno}).`

Hint: Check the address exists on this host and the port is permitted.

Cause: The listener could not bind for a reason other than a busy port (EACCES, EADDRNOTAVAIL…).

Resolution: Use an address assigned to this host and an unprivileged port.

Details:

| Field | Type | Required | Constraints |
|---|---|---|---|
| `host` | `string` | yes | — |
| `port` | `integer` | yes | — |
| `errno` | `string` | yes | — |

<a id="CONFIG_INVALID"></a>
### `CONFIG_INVALID`

| Property | Value |
|---|---|
| Title | Invalid configuration value |
| HTTP status | 400 |
| Category | `boot` |
| Retryable | `never` (do not retry; the request cannot succeed as sent) |
| Exit code | 64 |

Message: `invalid value for {source}: {reason}`

Hint: Run 'browserhive config validate' to see every problem.

Cause: A value failed its grammar, range or a cross-field rule.

Resolution: Fix the value at the named source; the grammar is in the message.

Details:

| Field | Type | Required | Constraints |
|---|---|---|---|
| `key` | `string` | yes | — |
| `source` | `string` | yes | — |
| `reason` | `string` | yes | — |

<a id="CONFIG_UNKNOWN_KEY"></a>
### `CONFIG_UNKNOWN_KEY`

| Property | Value |
|---|---|
| Title | Unknown configuration key |
| HTTP status | 400 |
| Category | `boot` |
| Retryable | `never` (do not retry; the request cannot succeed as sent) |
| Exit code | 64 |

Message: `unknown key '{key}' in {source}.`

Hint: Run 'browserhive --help' for the list of keys.

Cause: A flag, environment variable or config-file key is not in the registry (typo or removed knob).

Resolution: Use the suggested name; kebab-case flag spellings are not supported.

Details:

| Field | Type | Required | Constraints |
|---|---|---|---|
| `key` | `string` | yes | — |
| `source` | `string` | yes | — |
| `suggestion` | `string` | no | — |

<a id="BLOCKLIST_LOAD_FAILED"></a>
### `BLOCKLIST_LOAD_FAILED`

| Property | Value |
|---|---|
| Title | Blocklist could not be loaded |
| HTTP status | 400 |
| Category | `boot` |
| Retryable | `never` (do not retry; the request cannot succeed as sent) |
| Exit code | 3 |

Message: `Cannot load blocklist {path}: {reason}`

Hint: Check the path and file permissions.

Cause: The configured blocklist file is missing, unreadable or over the entry cap.

Resolution: Fix the file; a configured-but-unreadable blocklist is fatal by design.

Details:

| Field | Type | Required | Constraints |
|---|---|---|---|
| `path` | `string` | yes | — |
| `reason` | `string` | yes | — |

<a id="DATA_DIR_UNWRITABLE"></a>
### `DATA_DIR_UNWRITABLE`

| Property | Value |
|---|---|
| Title | Data directory not writable |
| HTTP status | 500 |
| Category | `boot` |
| Retryable | `never` (do not retry; the request cannot succeed as sent) |
| Exit code | 1 |

Message: `The data directory {path} is not writable.`

Hint: Fix permissions or choose another dataDir.

Cause: The data directory could not be created with mode 0700 or is owned by another user.

Resolution: Point `dataDir` at a directory the server user owns.

Details:

| Field | Type | Required | Constraints |
|---|---|---|---|
| `path` | `string` | yes | — |

<a id="DATA_DIR_LOCKED"></a>
### `DATA_DIR_LOCKED`

| Property | Value |
|---|---|
| Title | Data directory in use |
| HTTP status | 409 |
| Category | `boot` |
| Retryable | `never` (do not retry; the request cannot succeed as sent) |
| Exit code | 3 |

Message: `The data directory {path} is in use by another BrowserHive process (pid {pid}).`

Hint: Stop the running server first, or point dataDir at another directory.

Cause: A live process holds `<data-dir>/browserhive.lock`; one server (or one maintenance command) owns a data directory at a time.

Resolution: Stop that process. A lock left by a crashed process is detected (its pid is gone) and replaced automatically.

Details:

| Field | Type | Required | Constraints |
|---|---|---|---|
| `path` | `string` | yes | — |
| `pid` | `integer` | yes | — |

<a id="DB_OPEN_FAILED"></a>
### `DB_OPEN_FAILED`

| Property | Value |
|---|---|
| Title | Database could not be opened |
| HTTP status | 500 |
| Category | `boot` |
| Retryable | `never` (do not retry; the request cannot succeed as sent) |
| Exit code | 1 |

Message: `Cannot open database {path}: {reason}`

Hint: Run 'browserhive db status' for details.

Cause: SQLite refused to open the file (locked, permissions, wrong application_id).

Resolution: Check for another running server and file permissions.

Details:

| Field | Type | Required | Constraints |
|---|---|---|---|
| `path` | `string` | yes | — |
| `reason` | `string` | yes | — |

<a id="DB_NEWER_THAN_BINARY"></a>
### `DB_NEWER_THAN_BINARY`

| Property | Value |
|---|---|
| Title | Database newer than this version |
| HTTP status | 500 |
| Category | `boot` |
| Retryable | `never` (do not retry; the request cannot succeed as sent) |
| Exit code | 3 |

Message: `The database schema (v{db_version}, readable from v{min_reader_version}) is newer than this binary supports (v{binary_version}).`

Hint: Upgrade BrowserHive, or restore the pre-upgrade backup with 'browserhive db restore <file>'.

Cause: A newer BrowserHive migrated the database past this binary’s compatibility window (D-04).

Resolution: Upgrade, or restore the named backup to downgrade.

Details:

| Field | Type | Required | Constraints |
|---|---|---|---|
| `db_version` | `integer` | yes | — |
| `min_reader_version` | `integer` | yes | — |
| `binary_version` | `integer` | yes | — |
| `backup_path` | `string` | no | — |

<a id="MIGRATION_FAILED"></a>
### `MIGRATION_FAILED`

| Property | Value |
|---|---|
| Title | Migration failed |
| HTTP status | 500 |
| Category | `boot` |
| Retryable | `never` (do not retry; the request cannot succeed as sent) |
| Exit code | 1 |

Message: `Migration '{name}' (v{from} → v{to}) failed. The pre-migration backup is at {backup_path}.`

Hint: The database was rolled back; report the failure with the log.

Cause: A migration step raised inside the transaction; the transaction was rolled back.

Resolution: File a bug with the log; restore the backup if the database is unusable.

Details:

| Field | Type | Required | Constraints |
|---|---|---|---|
| `from` | `integer` | yes | — |
| `to` | `integer` | yes | — |
| `name` | `string` | yes | — |
| `backup_path` | `string` | yes | — |

<a id="DB_CORRUPT"></a>
### `DB_CORRUPT`

| Property | Value |
|---|---|
| Title | Database corrupt |
| HTTP status | 500 |
| Category | `boot` |
| Retryable | `never` (do not retry; the request cannot succeed as sent) |
| Exit code | 1 |

Message: `The database {path} is corrupt; it was moved to {quarantine_path}.`

Hint: Restore a backup with 'browserhive db restore <file>' or start fresh.

Cause: `PRAGMA quick_check` failed or SQLite reported corruption.

Resolution: Restore the most recent backup from `backups/`.

Details:

| Field | Type | Required | Constraints |
|---|---|---|---|
| `path` | `string` | yes | — |
| `quarantine_path` | `string` | yes | — |

<a id="UNHANDLED"></a>
### `UNHANDLED`

| Property | Value |
|---|---|
| Title | Unhandled error |
| HTTP status | 500 |
| Category | `boot` |
| Retryable | `never` (do not retry; the request cannot succeed as sent) |
| Exit code | 1 |

Message: `Unhandled {kind} reached the process handler: {name}.`

Hint: Check the log for the stack; the server keeps running when the failure is contained.

Cause: A promise rejected with no handler or an exception escaped every boundary; the CLI process handlers record it as a degradation (spec 10 §3).

Resolution: Report the log line; repeated occurrences on `/system.degradations` indicate a bug in a background task.

Details:

| Field | Type | Required | Constraints |
|---|---|---|---|
| `kind` | one of `exception`, `rejection` | yes | — |
| `name` | `string` | yes | — |

<a id="RETENTION_FAILED"></a>
### `RETENTION_FAILED`

| Property | Value |
|---|---|
| Title | Retention sweep failed |
| HTTP status | 500 |
| Category | `boot` |
| Retryable | `backoff` (retry later with backoff) |
| Exit code | 1 |

Message: `Retention step {step} failed: {reason}`

Hint: The sweep retries on the next interval; check disk space and file permissions.

Cause: A step of the periodic retention sweep (row pruning, byte cap, vacuum or artifact enqueue) threw. Recorded as a degradation, never fatal.

Resolution: Fix the underlying cause (disk, permissions, locks); the degradation resolves on the next clean pass.

Details:

| Field | Type | Required | Constraints |
|---|---|---|---|
| `step` | `string` | yes | — |
| `reason` | `string` | yes | — |

<a id="STALE_BROWSER_PROCESSES"></a>
### `STALE_BROWSER_PROCESSES`

| Property | Value |
|---|---|
| Title | Stale browser processes found |
| HTTP status | 500 |
| Category | `boot` |
| Retryable | `never` (do not retry; the request cannot succeed as sent) |
| Exit code | 1 |

Message: `{count} browser process(es) from a previous run are still alive.`

Hint: Stop them manually if they hold profile locks or memory.

Cause: Chromium processes launched by an earlier BrowserHive survived its exit (crash or SIGKILL). Recorded as a boot-time degradation, never fatal.

Resolution: Kill the listed processes; they are not adopted by the new server.

Details:

| Field | Type | Required | Constraints |
|---|---|---|---|
| `count` | `integer` | yes | — |

## Authentication errors

Returned by the REST API, `/mcp` and the WebSocket handshake.

| Code | Title | HTTP | Retryable |
|---|---|---|---|
| [`UNAUTHORIZED`](#UNAUTHORIZED) | Unauthorized | 401 | never |
| [`INVALID_CREDENTIALS`](#INVALID_CREDENTIALS) | Invalid credentials | 401 | never |
| [`FORBIDDEN`](#FORBIDDEN) | Forbidden | 403 | never |
| [`PASSWORD_CHANGE_REQUIRED`](#PASSWORD_CHANGE_REQUIRED) | Password change required | 403 | after_operator |
| [`BAD_CURRENT_PASSWORD`](#BAD_CURRENT_PASSWORD) | Current password incorrect | 400 | different_args |
| [`WEAK_PASSWORD`](#WEAK_PASSWORD) | Password too weak | 400 | different_args |

<a id="UNAUTHORIZED"></a>
### `UNAUTHORIZED`

| Property | Value |
|---|---|
| Title | Unauthorized |
| HTTP status | 401 |
| Category | `auth` |
| Retryable | `never` (do not retry; the request cannot succeed as sent) |

Message: `Authentication required.`

Hint: Log in, or send a valid bearer token.

Cause: No credential was presented, or the presented one is invalid or expired.

Resolution: Authenticate with the dashboard session cookie or a bearer token.

Details: none.

<a id="INVALID_CREDENTIALS"></a>
### `INVALID_CREDENTIALS`

| Property | Value |
|---|---|
| Title | Invalid credentials |
| HTTP status | 401 |
| Category | `auth` |
| Retryable | `never` (do not retry; the request cannot succeed as sent) |

Message: `Invalid username or password.`

Hint: Check the password; reset it with browserhive admin reset-password.

Cause: The login password did not match, or the account is locked out.

Resolution: Retry with the correct password or reset it from the CLI.

Details: none.

<a id="FORBIDDEN"></a>
### `FORBIDDEN`

| Property | Value |
|---|---|
| Title | Forbidden |
| HTTP status | 403 |
| Category | `auth` |
| Retryable | `never` (do not retry; the request cannot succeed as sent) |

Message: `This action requires the '{scope}' scope.`

Hint: Use a principal that holds the scope.

Cause: The principal lacks the scope the route or tool declares.

Resolution: Operators hold every scope; issue an operator token with the needed scope.

Details:

| Field | Type | Required | Constraints |
|---|---|---|---|
| `scope` | `string` | yes | — |

<a id="PASSWORD_CHANGE_REQUIRED"></a>
### `PASSWORD_CHANGE_REQUIRED`

| Property | Value |
|---|---|
| Title | Password change required |
| HTTP status | 403 |
| Category | `auth` |
| Retryable | `after_operator` (retry after an operator acts (unlock the vault, resolve a request, change policy)) |

Message: `The seed password must be changed before continuing.`

Hint: Change the password on the login screen.

Cause: The operator account still uses the generated seed password.

Resolution: Set a new password; the seed file is shredded afterwards.

Details: none.

<a id="BAD_CURRENT_PASSWORD"></a>
### `BAD_CURRENT_PASSWORD`

| Property | Value |
|---|---|
| Title | Current password incorrect |
| HTTP status | 400 |
| Category | `auth` |
| Retryable | `different_args` (retry only with different arguments) |

Message: `The current password is incorrect.`

Hint: Re-enter the current password.

Cause: The password-change request carried a wrong current password.

Resolution: Retry with the correct current password.

Details: none.

<a id="WEAK_PASSWORD"></a>
### `WEAK_PASSWORD`

| Property | Value |
|---|---|
| Title | Password too weak |
| HTTP status | 400 |
| Category | `auth` |
| Retryable | `different_args` (retry only with different arguments) |

Message: `The new password must be at least {min_length} characters long.`

Hint: Choose a longer password.

Cause: The new password is shorter than the minimum length.

Resolution: Use at least the minimum number of characters.

Details:

| Field | Type | Required | Constraints |
|---|---|---|---|
| `min_length` | `integer` | yes | — |

## Transport errors

Protocol-level failures of HTTP and WebSocket requests (validation, limits, conflicts).

| Code | Title | HTTP | Retryable |
|---|---|---|---|
| [`ORIGIN_NOT_ALLOWED`](#ORIGIN_NOT_ALLOWED) | Origin not allowed | 403 | never |
| [`HOST_NOT_ALLOWED`](#HOST_NOT_ALLOWED) | Host not allowed | 421 | never |
| [`RATE_LIMITED`](#RATE_LIMITED) | Rate limited | 429 | backoff |
| [`PAYLOAD_TOO_LARGE`](#PAYLOAD_TOO_LARGE) | Payload too large | 413 | different_args |
| [`VALIDATION_FAILED`](#VALIDATION_FAILED) | Validation failed | 400 | different_args |
| [`NOT_FOUND`](#NOT_FOUND) | Not found | 404 | never |
| [`METHOD_NOT_ALLOWED`](#METHOD_NOT_ALLOWED) | Method not allowed | 405 | never |
| [`CONFLICT`](#CONFLICT) | Conflict | 409 | different_args |
| [`NOT_ACCEPTABLE`](#NOT_ACCEPTABLE) | Not acceptable | 406 | different_args |
| [`WS_PROTOCOL_ERROR`](#WS_PROTOCOL_ERROR) | WebSocket protocol error | 400 | never |
| [`WS_OVERLOADED`](#WS_OVERLOADED) | WebSocket overloaded | 503 | backoff |

<a id="ORIGIN_NOT_ALLOWED"></a>
### `ORIGIN_NOT_ALLOWED`

| Property | Value |
|---|---|
| Title | Origin not allowed |
| HTTP status | 403 |
| Category | `transport` |
| Retryable | `never` (do not retry; the request cannot succeed as sent) |

Message: `The request Origin is not allowed.`

Hint: Open the dashboard from the server URL itself.

Cause: A browser request carried an `Origin` that does not match the server (CSRF guard).

Resolution: Use the dashboard served by this BrowserHive; no cross-origin access is allowed.

Details: none.

<a id="HOST_NOT_ALLOWED"></a>
### `HOST_NOT_ALLOWED`

| Property | Value |
|---|---|
| Title | Host not allowed |
| HTTP status | 421 |
| Category | `transport` |
| Retryable | `never` (do not retry; the request cannot succeed as sent) |

Message: `The request Host header is not allowed.`

Hint: Address the server by the host it is bound to.

Cause: DNS-rebinding guard: the `Host` header does not match the bound host.

Resolution: Use the bound host or IP in the URL.

Details: none.

<a id="RATE_LIMITED"></a>
### `RATE_LIMITED`

| Property | Value |
|---|---|
| Title | Rate limited |
| HTTP status | 429 |
| Category | `transport` |
| Retryable | `backoff` (retry later with backoff) |

Message: `Too many requests. Retry after {retry_after_ms} ms.`

Hint: Back off for the given interval.

Cause: The client exceeded the login or API rate limit.

Resolution: Wait for `retry_after_ms` and retry.

Details:

| Field | Type | Required | Constraints |
|---|---|---|---|
| `retry_after_ms` | `integer` | yes | — |

<a id="PAYLOAD_TOO_LARGE"></a>
### `PAYLOAD_TOO_LARGE`

| Property | Value |
|---|---|
| Title | Payload too large |
| HTTP status | 413 |
| Category | `transport` |
| Retryable | `different_args` (retry only with different arguments) |

Message: `The request body exceeds the limit of {limit_bytes} bytes.`

Hint: Send a smaller body.

Cause: The body is larger than the route’s limit.

Resolution: Reduce the payload size.

Details:

| Field | Type | Required | Constraints |
|---|---|---|---|
| `limit_bytes` | `integer` | yes | — |

<a id="VALIDATION_FAILED"></a>
### `VALIDATION_FAILED`

| Property | Value |
|---|---|
| Title | Validation failed |
| HTTP status | 400 |
| Category | `transport` |
| Retryable | `different_args` (retry only with different arguments) |

Message: `The request failed validation.`

Hint: Each issue names the field and what is wrong.

Cause: Params, query or body did not match the route schema; unknown keys are errors.

Resolution: Fix the listed fields.

Details:

| Field | Type | Required | Constraints |
|---|---|---|---|
| `issues` | `object[]` | yes | each item: keys `path`, `message`, `code` |

<a id="NOT_FOUND"></a>
### `NOT_FOUND`

| Property | Value |
|---|---|
| Title | Not found |
| HTTP status | 404 |
| Category | `transport` |
| Retryable | `never` (do not retry; the request cannot succeed as sent) |

Message: `The requested resource does not exist.`

Hint: Check the path and id.

Cause: No route or resource matches the request.

Resolution: Consult /api/v1/docs for the route table.

Details: none.

<a id="METHOD_NOT_ALLOWED"></a>
### `METHOD_NOT_ALLOWED`

| Property | Value |
|---|---|
| Title | Method not allowed |
| HTTP status | 405 |
| Category | `transport` |
| Retryable | `never` (do not retry; the request cannot succeed as sent) |

Message: `The method is not allowed for this resource.`

Hint: Use one of the allowed methods.

Cause: The route exists but not for this HTTP method.

Resolution: See `details.allow` and the `Allow` header.

Details:

| Field | Type | Required | Constraints |
|---|---|---|---|
| `allow` | `string[]` | yes | — |

<a id="CONFLICT"></a>
### `CONFLICT`

| Property | Value |
|---|---|
| Title | Conflict |
| HTTP status | 409 |
| Category | `transport` |
| Retryable | `different_args` (retry only with different arguments) |

Message: `The resource changed since you last read it.`

Hint: Reload the resource and retry with the current version.

Cause: An optimistic-concurrency check (`If-Match`/version) failed; the HTTP layer may use 412.

Resolution: Re-read, then resubmit with `current_version`.

Details:

| Field | Type | Required | Constraints |
|---|---|---|---|
| `current_version` | `integer` | no | — |

<a id="NOT_ACCEPTABLE"></a>
### `NOT_ACCEPTABLE`

| Property | Value |
|---|---|
| Title | Not acceptable |
| HTTP status | 406 |
| Category | `transport` |
| Retryable | `different_args` (retry only with different arguments) |

Message: `None of the requested media types is supported.`

Hint: Ask for one of the supported types.

Cause: The `Accept` header lists no representation the route can produce.

Resolution: See `details.supported`.

Details:

| Field | Type | Required | Constraints |
|---|---|---|---|
| `supported` | `string[]` | yes | — |

<a id="WS_PROTOCOL_ERROR"></a>
### `WS_PROTOCOL_ERROR`

| Property | Value |
|---|---|
| Title | WebSocket protocol error |
| HTTP status | 400 |
| Category | `transport` |
| Retryable | `never` (do not retry; the request cannot succeed as sent) |

Message: `The WebSocket client violated the protocol.`

Hint: Reconnect with a compliant client.

Cause: Malformed envelope, unknown command, wrong protocol version, or a command before subscribe.

Resolution: Fix the client; the socket is closed with the code in spec 03 §6.4.

Details:

| Field | Type | Required | Constraints |
|---|---|---|---|
| `violations` | `string[]` | yes | — |

<a id="WS_OVERLOADED"></a>
### `WS_OVERLOADED`

| Property | Value |
|---|---|
| Title | WebSocket overloaded |
| HTTP status | 503 |
| Category | `transport` |
| Retryable | `backoff` (retry later with backoff) |

Message: `The connection fell too far behind ({buffered_bytes} bytes buffered).`

Hint: Reconnect and resubscribe with the last cursor.

Cause: Backpressure: the client did not drain the feed within the buffer bounds.

Resolution: Reconnect; the feed replays from your cursor or asks for a resync.

Details:

| Field | Type | Required | Constraints |
|---|---|---|---|
| `buffered_bytes` | `integer` | yes | — |

## Audit outcomes

Recorded in the audit trail (vault log, attention history). Some are also returned inside a tool result.

| Code | Title | HTTP | Retryable |
|---|---|---|---|
| [`ORIGIN_MISMATCH`](#ORIGIN_MISMATCH) | Origin not on the allow-list | 403 | different_args |
| [`VAULT_FILL_AUTH_FAILED`](#VAULT_FILL_AUTH_FAILED) | Vault fill: authentication failed | 500 | never |
| [`VAULT_FILL_BLOCKED`](#VAULT_FILL_BLOCKED) | Vault fill: blocked | 500 | never |
| [`VAULT_LIST_DENIED`](#VAULT_LIST_DENIED) | Vault list: denied | 500 | never |
| [`ATTENTION_REJECTED`](#ATTENTION_REJECTED) | Attention: rejected | 500 | never |
| [`ATTENTION_TIMEOUT`](#ATTENTION_TIMEOUT) | Attention: timed out | 500 | never |
| [`ATTENTION_CANCELLED`](#ATTENTION_CANCELLED) | Attention: cancelled | 500 | never |

<a id="ORIGIN_MISMATCH"></a>
### `ORIGIN_MISMATCH`

| Property | Value |
|---|---|
| Title | Origin not on the allow-list |
| HTTP status | 403 |
| Category | `audit` |
| Retryable | `different_args` (retry only with different arguments) |

Message: `Origin '{page_origin}' is not on the allow-list for vault entry '{entry_name}'.`

Hint: Navigate to an allowed origin before filling.

Cause: The page origin at fill time did not match the binding’s allowed origins.

Resolution: Fill only on the sites the operator bound the entry to.

Details:

| Field | Type | Required | Constraints |
|---|---|---|---|
| `page_origin` | `string` | yes | — |
| `entry_name` | `string` | yes | — |
| `allowed` | `string[]` | yes | — |

<a id="VAULT_FILL_AUTH_FAILED"></a>
### `VAULT_FILL_AUTH_FAILED`

| Property | Value |
|---|---|
| Title | Vault fill: authentication failed |
| HTTP status | 500 |
| Category | `audit` |
| Retryable | `never` (do not retry; the request cannot succeed as sent) |

Message: `The vault fill completed but the site did not accept the credentials.`

Hint: Classification of a returned vault_fill status; not an exception.

Cause: `vault_fill` returned `auth_failed`.

Resolution: Check the credential in the vault; nothing to retry automatically.

Details:

| Field | Type | Required | Constraints |
|---|---|---|---|
| `entry_name` | `string` | yes | — |

<a id="VAULT_FILL_BLOCKED"></a>
### `VAULT_FILL_BLOCKED`

| Property | Value |
|---|---|
| Title | Vault fill: blocked |
| HTTP status | 500 |
| Category | `audit` |
| Retryable | `never` (do not retry; the request cannot succeed as sent) |

Message: `The vault fill was blocked by policy.`

Hint: Classification of a returned vault_fill status; not an exception.

Cause: `vault_fill` returned `blocked` (policy gate refused).

Resolution: See the reason; operators adjust bindings and policies.

Details:

| Field | Type | Required | Constraints |
|---|---|---|---|
| `entry_name` | `string` | yes | — |
| `reason` | `string` | no | — |

<a id="VAULT_LIST_DENIED"></a>
### `VAULT_LIST_DENIED`

| Property | Value |
|---|---|
| Title | Vault list: denied |
| HTTP status | 500 |
| Category | `audit` |
| Retryable | `never` (do not retry; the request cannot succeed as sent) |

Message: `The vault listing was denied for this session.`

Hint: Classification of a returned vault_list_available status; not an exception.

Cause: `vault_list_available` returned no entries because policy denied the session.

Resolution: Operators adjust bindings and policies.

Details: none.

<a id="ATTENTION_REJECTED"></a>
### `ATTENTION_REJECTED`

| Property | Value |
|---|---|
| Title | Attention: rejected |
| HTTP status | 500 |
| Category | `audit` |
| Retryable | `never` (do not retry; the request cannot succeed as sent) |

Message: `The attention request was rejected.`

Hint: Classification of a returned request_attention status; not an exception.

Cause: The operator rejected the request, or the session closed while it was open.

Resolution: Read the returned message; continue or stop the task accordingly.

Details:

| Field | Type | Required | Constraints |
|---|---|---|---|
| `request_id` | `string` | yes | — |

<a id="ATTENTION_TIMEOUT"></a>
### `ATTENTION_TIMEOUT`

| Property | Value |
|---|---|
| Title | Attention: timed out |
| HTTP status | 500 |
| Category | `audit` |
| Retryable | `never` (do not retry; the request cannot succeed as sent) |

Message: `Attention request timed out; the operator was not available to respond.`

Hint: Classification of a returned request_attention status; not an exception.

Cause: Nobody resolved the request before its deadline.

Resolution: Retry later with a longer wait, or proceed without the operator.

Details:

| Field | Type | Required | Constraints |
|---|---|---|---|
| `request_id` | `string` | yes | — |

<a id="ATTENTION_CANCELLED"></a>
### `ATTENTION_CANCELLED`

| Property | Value |
|---|---|
| Title | Attention: cancelled |
| HTTP status | 500 |
| Category | `audit` |
| Retryable | `never` (do not retry; the request cannot succeed as sent) |

Message: `Client cancelled the attention request.`

Hint: Classification of a returned request_attention status; not an exception.

Cause: The MCP client disconnected or sent notifications/cancelled.

Resolution: Nothing to do.

Details:

| Field | Type | Required | Constraints |
|---|---|---|---|
| `request_id` | `string` | yes | — |

## Warnings

Non-fatal degradations recorded on a session or in the system events list; the operation continues.

| Code | Title | HTTP | Retryable |
|---|---|---|---|
| [`EXECUTABLE_PATH_OVERRIDE`](#EXECUTABLE_PATH_OVERRIDE) | Executable path override | 500 | never |
| [`TRACE_START_FAILED`](#TRACE_START_FAILED) | Trace could not start | 500 | never |
| [`TRACE_FINALIZE_FAILED`](#TRACE_FINALIZE_FAILED) | Trace could not be finalized | 500 | never |
| [`STEALTH_INIT_FAILED`](#STEALTH_INIT_FAILED) | Stealth init failed | 500 | never |
| [`BLOCKLIST_ROUTE_FAILED`](#BLOCKLIST_ROUTE_FAILED) | Blocklist route failed | 500 | never |
| [`BYO_PROXY_UNSEEDED`](#BYO_PROXY_UNSEEDED) | BYO proxy: geo not seeded | 500 | never |
| [`VIEWPORT_OVERRIDE_UNASSERTED`](#VIEWPORT_OVERRIDE_UNASSERTED) | Viewport override: display not asserted | 500 | never |
| [`IDENTITY_SEED_SAVE_FAILED`](#IDENTITY_SEED_SAVE_FAILED) | Identity seed not saved | 500 | never |
| [`REAP_DEAD_FAILED`](#REAP_DEAD_FAILED) | Dead session reap failed | 500 | never |
| [`CDP_SESSION_LEAKED`](#CDP_SESSION_LEAKED) | CDP session leaked | 500 | never |
| [`SCREENSHOT_ARCHIVE_FAILED`](#SCREENSHOT_ARCHIVE_FAILED) | Screenshot archive failed | 500 | never |

<a id="EXECUTABLE_PATH_OVERRIDE"></a>
### `EXECUTABLE_PATH_OVERRIDE`

| Property | Value |
|---|---|
| Title | Executable path override |
| HTTP status | 500 |
| Category | `warning` |
| Retryable | `never` (do not retry; the request cannot succeed as sent) |

Message: `{message}`

Hint: Session warning: logged and broadcast on session:<id>, never thrown.

Cause: A launch option set `executablePath`, which disables channel routing.

Resolution: Prefer `channel`; use `executablePath` only for custom builds.

Details:

| Field | Type | Required | Constraints |
|---|---|---|---|
| `session_id` | `string` | yes | — |
| `message` | `string` | yes | — |
| `details` | `object` | no | additional keys allowed |

<a id="TRACE_START_FAILED"></a>
### `TRACE_START_FAILED`

| Property | Value |
|---|---|
| Title | Trace could not start |
| HTTP status | 500 |
| Category | `warning` |
| Retryable | `never` (do not retry; the request cannot succeed as sent) |

Message: `{message}`

Hint: Session warning: logged and broadcast on session:<id>, never thrown.

Cause: Playwright tracing failed to start for the session; the session continues without a trace.

Resolution: Check disk space and the session directory permissions.

Details:

| Field | Type | Required | Constraints |
|---|---|---|---|
| `session_id` | `string` | yes | — |
| `message` | `string` | yes | — |
| `details` | `object` | no | additional keys allowed |

<a id="TRACE_FINALIZE_FAILED"></a>
### `TRACE_FINALIZE_FAILED`

| Property | Value |
|---|---|
| Title | Trace could not be finalized |
| HTTP status | 500 |
| Category | `warning` |
| Retryable | `never` (do not retry; the request cannot succeed as sent) |

Message: `{message}`

Hint: Session warning: logged and broadcast on session:<id>, never thrown.

Cause: Stopping the trace failed or exceeded the 10 s cap at close.

Resolution: The trace may be incomplete; check disk space.

Details:

| Field | Type | Required | Constraints |
|---|---|---|---|
| `session_id` | `string` | yes | — |
| `message` | `string` | yes | — |
| `details` | `object` | no | additional keys allowed |

<a id="STEALTH_INIT_FAILED"></a>
### `STEALTH_INIT_FAILED`

| Property | Value |
|---|---|
| Title | Stealth init failed |
| HTTP status | 500 |
| Category | `warning` |
| Retryable | `never` (do not retry; the request cannot succeed as sent) |

Message: `{message}`

Hint: Session warning: logged and broadcast on session:<id>, never thrown.

Cause: The CDP identity override could not be applied; the session runs with reduced stealth.

Resolution: Check the driver (Patchright/Playwright) version compatibility.

Details:

| Field | Type | Required | Constraints |
|---|---|---|---|
| `session_id` | `string` | yes | — |
| `message` | `string` | yes | — |
| `details` | `object` | no | additional keys allowed |

<a id="BLOCKLIST_ROUTE_FAILED"></a>
### `BLOCKLIST_ROUTE_FAILED`

| Property | Value |
|---|---|
| Title | Blocklist route failed |
| HTTP status | 500 |
| Category | `warning` |
| Retryable | `never` (do not retry; the request cannot succeed as sent) |

Message: `{message}`

Hint: Session warning: logged and broadcast on session:<id>, never thrown.

Cause: The network-level blocklist route could not be installed; tool-level enforcement still applies.

Resolution: Check the driver version; report if it repeats.

Details:

| Field | Type | Required | Constraints |
|---|---|---|---|
| `session_id` | `string` | yes | — |
| `message` | `string` | yes | — |
| `details` | `object` | no | additional keys allowed |

<a id="BYO_PROXY_UNSEEDED"></a>
### `BYO_PROXY_UNSEEDED`

| Property | Value |
|---|---|
| Title | BYO proxy: geo not seeded |
| HTTP status | 500 |
| Category | `warning` |
| Retryable | `never` (do not retry; the request cannot succeed as sent) |

Message: `{message}`

Hint: Session warning: logged and broadcast on session:<id>, never thrown.

Cause: A caller-supplied proxy suppresses geo-derived identity because the exit location is unknown.

Resolution: Pass `locale`/`timezoneId` explicitly when using your own proxy.

Details:

| Field | Type | Required | Constraints |
|---|---|---|---|
| `session_id` | `string` | yes | — |
| `message` | `string` | yes | — |
| `details` | `object` | no | additional keys allowed |

<a id="VIEWPORT_OVERRIDE_UNASSERTED"></a>
### `VIEWPORT_OVERRIDE_UNASSERTED`

| Property | Value |
|---|---|
| Title | Viewport override: display not asserted |
| HTTP status | 500 |
| Category | `warning` |
| Retryable | `never` (do not retry; the request cannot succeed as sent) |

Message: `{message}`

Hint: Session warning: logged and broadcast on session:<id>, never thrown.

Cause: A caller-supplied viewport disables the display-coherence assertion of the fingerprint.

Resolution: Omit `viewport` to let the identity pick a coherent display.

Details:

| Field | Type | Required | Constraints |
|---|---|---|---|
| `session_id` | `string` | yes | — |
| `message` | `string` | yes | — |
| `details` | `object` | no | additional keys allowed |

<a id="IDENTITY_SEED_SAVE_FAILED"></a>
### `IDENTITY_SEED_SAVE_FAILED`

| Property | Value |
|---|---|
| Title | Identity seed not saved |
| HTTP status | 500 |
| Category | `warning` |
| Retryable | `never` (do not retry; the request cannot succeed as sent) |

Message: `{message}`

Hint: Session warning: logged and broadcast on session:<id>, never thrown.

Cause: The `.identity.json` sidecar could not be written with the full profile.

Resolution: Check the auth-states directory permissions.

Details:

| Field | Type | Required | Constraints |
|---|---|---|---|
| `session_id` | `string` | yes | — |
| `message` | `string` | yes | — |
| `details` | `object` | no | additional keys allowed |

<a id="REAP_DEAD_FAILED"></a>
### `REAP_DEAD_FAILED`

| Property | Value |
|---|---|
| Title | Dead session reap failed |
| HTTP status | 500 |
| Category | `warning` |
| Retryable | `never` (do not retry; the request cannot succeed as sent) |

Message: `{message}`

Hint: Session warning: logged and broadcast on session:<id>, never thrown.

Cause: Closing a dead session raised; the sweeper will retry.

Resolution: No action; report if it repeats for the same session.

Details:

| Field | Type | Required | Constraints |
|---|---|---|---|
| `session_id` | `string` | yes | — |
| `message` | `string` | yes | — |
| `details` | `object` | no | additional keys allowed |

<a id="CDP_SESSION_LEAKED"></a>
### `CDP_SESSION_LEAKED`

| Property | Value |
|---|---|
| Title | CDP session leaked |
| HTTP status | 500 |
| Category | `warning` |
| Retryable | `never` (do not retry; the request cannot succeed as sent) |

Message: `{message}`

Hint: Session warning: logged and broadcast on session:<id>, never thrown.

Cause: A CDP session was still attached when its page closed and had to be pruned late.

Resolution: No action; report if it repeats.

Details:

| Field | Type | Required | Constraints |
|---|---|---|---|
| `session_id` | `string` | yes | — |
| `message` | `string` | yes | — |
| `details` | `object` | no | additional keys allowed |

<a id="SCREENSHOT_ARCHIVE_FAILED"></a>
### `SCREENSHOT_ARCHIVE_FAILED`

| Property | Value |
|---|---|
| Title | Screenshot archive failed |
| HTTP status | 500 |
| Category | `warning` |
| Retryable | `never` (do not retry; the request cannot succeed as sent) |

Message: `{message}`

Hint: Session warning: logged and broadcast on session:<id>, never thrown.

Cause: A tool screenshot could not be written to the session directory.

Resolution: Check disk space and permissions.

Details:

| Field | Type | Required | Constraints |
|---|---|---|---|
| `session_id` | `string` | yes | — |
| `message` | `string` | yes | — |
| `details` | `object` | no | additional keys allowed |