Basilic
Architecture

Error Handling

Two-track errors: captureError logs the real failure; HTTP clients get a catalog { code, message }.

@repo/error extracts messages (getErrorMessage, tryCatch) and logs failures (captureError). Each app owns its catalog (code + user-safe message). Import by runtime:

  • @repo/errorgetErrorMessage / tryCatch only (safe in RSC)
  • @repo/error/node — Fastify / Node (default server logger)
  • @repo/error/nextjs — browser (error.tsx); default client logger
  • @repo/error/nextjs/server — Route Handlers / server instrumentation
  • @repo/error/browser, @repo/error/react — SPA / ErrorBoundary

captureError is log-only and synchronous. It does not call Sentry. report: false emits nothing (tests). @sentry/* packages stay installed; initErrorReporting is a no-op; Next onRequestError / onRouterTransitionStart are not wired to Sentry. withSentryConfig still gates on SENTRY_AUTH_TOKEN. To re-enable later: restore Sentry.init in initErrorReporting and a capture adapter — that is not current behavior.

Two tracks: logs get { err, label, code, ... }; the HTTP response is { code, message } from the catalog. Never send connection strings or stack traces to clients.

import { captureError } from '@repo/error/node'
import { getErrorMessage } from '@repo/error'

captureError({ error, label: 'Database', tags: { app: 'api' } })

On the API, sendCatalogError replies only and never logs. Thrown status >= 500 is captured once in the error handler (method, path-only URL, code, err — no headers or body). Handled 500s use sendServerCatalogError (one captureError then catalog). AI 502/504/402: only handleUpstreamError captures.

import { sendCatalogError } from '../lib/catalogs/mapper.js'

return sendCatalogError({ reply, status: 401, code: 'UNAUTHORIZED' })

React subtrees: react-error-boundary plus only captureError (no extra logger.error). See Frontend. Logging: Logging.