# Errors

One shape crosses every boundary: `ArbeError`. Both the throwable class — internals `throw new ArbeError({ code, message, suggestion?, recoverable?, context? })` instead of bare `Error` — and the validated wire schema HTTP and CLI surfaces emit. `code` is dotted lowercase, namespaced by domain (`task.already_claimed`, `validation.invalid_input`, `auth.forbidden`, `agent.dispatch_failed`). Grow the enum by adding; never renumber or reuse.

```
HTTP            { error: ArbeError.toJSON() }      status from code map (no per-throw override)
CLI stderr      error: <message>                    →  <suggestion>
CLI --json      { error: ArbeErrorPayload }        identical to HTTP body
JS client       rejects with parsed ArbeError instance
```

HTTP status derives from code via a single map — if you need a new status for an existing code, add a new code instead. This rules out "`record.not_found` that returns 500" by construction. Boundary layers (SvelteKit `+server` handlers, worker routes, `apps/cli/src/cli.ts`) catch `ArbeError`, log the underlying cause, serialise. Unknown throws go through `toArbeError()` which wraps them as `server.internal` with the original as `cause` so crashes still render through the canonical path.

Internal helpers that never cross a boundary still throw native `Error` — cheap, idiomatic, won't change. Code a consumer will see the failure of (CLI commands, HTTP endpoints, mutation executors) throws `ArbeError`. The line between "internal" and "boundary-facing" is the discriminator between developers-see-this-in-logs and users-see-this-on-their-screen — when in doubt, throw `ArbeError`; the cost is one import and one extra field.

We explicitly rejected the `Result<Success|Failure>` discriminated-union pattern. Throws propagate; only boundaries catch. Wrapping every function's return type in a union is ceremony arbe's code-style argues against ("optimistic execution — let errors throw"). `ArbeError` is the **shape**, not the **protocol**. When a new failure mode is genuinely a variant of an existing code, prefer reusing the code and putting distinguishing detail in `message` / `context` rather than minting a new code — codes are for switches that branch consumer behaviour; messages are for humans.

Code: `@arbe/errors` (`ArbeError`, `ArbeErrorCodeSchema`, `toArbeError`), re-exported via `@arbe/core/schemas/arbe-error`. Boundary catchers: SvelteKit `+server` handlers, worker routes, `apps/cli/src/cli.ts`.
