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/error—getErrorMessage/tryCatchonly (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.