Skip to content
View as .md

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 ArbeError.toJSON() as the whole body, unwrapped — no { error: … } envelope
status from code map (no per-throw override)
CLI stderr error: <message> → <suggestion>
CLI --json { error: ArbeErrorPayload } the payload, wrapped for the CLI's JSON envelope
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.

Website failures are reported once at the response boundary. Unhandled throws and handled 5xx errors share a request ID across the server log, PostHog, and the response (errorId, also in context for CLI/SDK consumers). Unknown-route 404s stay quiet; file paths are never rejected merely for looking like scanner probes. Browser operational reports use one anonymous direct sender regardless of product-analytics consent or SDK readiness. The www server, browser and backstage send native $exception events to PostHog Error Tracking with the exception message, stack, runtime, source and release SHA. Emails, bearer tokens, API keys, JWTs and sk-... values are redacted from messages and stacks; arbitrary context is excluded. User-caused 4xx responses are excluded. Reporting is always-on operational telemetry, independent of product-analytics consent.