# Bun (Sentry-compatible SDK)

> Use the official Sentry SDK for Bun with a Belvador DSN, Belvador's scrubbing and an on-disk queue while your node is unreachable.

For Bun servers Belvador uses the **official Sentry SDK for Bun** with your Belvador DSN and Belvador's own scrubbing from `@belvador/core/sentry`. Add `@belvador/spool` and errors that cannot be sent wait on disk until your node answers, also across a crash or a restart.

> [!NOTE]
> `@sentry/bun` is Sentry's SDK, not a Belvador package. Belvador runs this exact recipe with `@sentry/bun` 11.9.0 against a real node: an error thrown in a `Bun.serve` handler, an uncaught crash, and errors made while the node was unreachable before the process was killed each arrive once.

## 1. Install

```bash
bun add @sentry/bun@11.9.0 @belvador/core@0.6.1
```

## 2. Create `instrument.ts`

```ts title="instrument.ts"
import * as Sentry from "@sentry/bun";
import { scrubBreadcrumb, scrubEvent } from "@belvador/core/sentry";

Sentry.init({
  dsn: process.env.BELVADOR_DSN ?? "https://<public-key>@ingest.belvador.dev/<project-id>",
  release: process.env.APP_VERSION,
  environment: process.env.NODE_ENV,
  sendDefaultPii: false,
  tracesSampleRate: 0,
  maxBreadcrumbs: 50,
  maxValueLength: 8192,
  // Belvador's scrubbing: request bodies, cookies and credentials never leave the app.
  beforeSend: scrubEvent,
  beforeBreadcrumb: scrubBreadcrumb,
});
```

The options are the same as for [Node.js](https://belvador.dev/en/docs/sdk/node#2-create-instrument-mjs): no personal data or spans from the SDK, Belvador's bounds, and `beforeSend: scrubEvent` / `beforeBreadcrumb: scrubBreadcrumb` — the scrubbing Belvador's own SDKs run, so request bodies, cookies, auth headers and credentials in text never leave your server.

## 3. Load it before your app

```bash
bun --preload ./instrument.ts server.ts
```

`--preload` runs the file before your entry point, so `Bun.serve` is instrumented when your app starts it.

## Errors in `Bun.serve`

```ts title="server.ts"
// An error a Bun.serve handler throws is reported by @sentry/bun; the client gets a 500.
Bun.serve({
  port: Number(process.env.PORT ?? 3000),
  async fetch(req) {
    const url = new URL(req.url);
    if (url.pathname === "/invoices") return Response.json(await listInvoices());
    return new Response("Not found", { status: 404 });
  },
});
```

An error your `fetch` handler throws is reported by `@sentry/bun` and the client gets a `500`. Errors you handle yourself: `Sentry.captureException(error)`.

## Keep errors when your node is unreachable

```ts title="instrument.ts"
import * as Sentry from "@sentry/bun";
import { scrubBreadcrumb, scrubEvent } from "@belvador/core/sentry";
import { withSpool } from "@belvador/spool";

Sentry.init({
  dsn: process.env.BELVADOR_DSN ?? "https://<public-key>@ingest.belvador.dev/<project-id>",
  release: process.env.APP_VERSION,
  environment: process.env.NODE_ENV,
  sendDefaultPii: false,
  tracesSampleRate: 0,
  maxBreadcrumbs: 50,
  maxValueLength: 8192,
  // Belvador's scrubbing: request bodies, cookies and credentials never leave the app.
  beforeSend: scrubEvent,
  beforeBreadcrumb: scrubBreadcrumb,
  // Errors that cannot be sent wait on disk and go out after a restart.
  transport: withSpool(Sentry.makeFetchTransport, {
    dir: process.env.BELVADOR_SPOOL_DIR ?? "./.belvador-spool",
  }),
});
```

Errors wait in `BELVADOR_SPOOL_DIR` (at most 64 MiB, 72 hours) and are sent by this process or the next one once your node answers. Details: [Offline and durable delivery](https://belvador.dev/en/docs/sdk/offline).

## Database errors

`{ data, error }` results from Supabase or PostgREST: bind `captureResult` to `@sentry/bun` as shown in [Database errors](https://belvador.dev/en/docs/sdk/database-errors#on-an-official-sentry-sdk), with `import * as Sentry from "@sentry/bun"`.

## Set it up with an AI agent

Paste this into Claude Code, Cursor or another coding agent. It contains the files above and the rules it must follow:

````text
Belvador errors: Bun (@sentry/bun)

Tested with: @sentry/* 11.9.0
Guide: https://belvador.dev/en/docs/sdk/bun

Goal
Report this server's errors to Belvador with the official Sentry SDK and Belvador's scrubbing, exactly as in the files below. Keep any other error SDK out: one Sentry.init per process.

Rules
Copy the files as they are and keep every Sentry.init option: sendDefaultPii: false, tracesSampleRate: 0, maxBreadcrumbs: 50, beforeSend: scrubEvent, beforeBreadcrumb: scrubBreadcrumb. Read the DSN from BELVADOR_DSN; do not invent other environment variables. Do not change authentication, routing or business logic. Report errors the code already handles with captureException (report() on Supabase Edge Functions) and wrap Supabase or PostgREST results that return an error instead of throwing with captureResult, without changing what the code does with the result.

```bash
bun add @sentry/bun@11.9.0 @belvador/core@0.6.1
```

```bash
npm install @belvador/spool@0.6.1
```

instrument.ts
```typescript
import * as Sentry from "@sentry/bun";
import { scrubBreadcrumb, scrubEvent } from "@belvador/core/sentry";
import { withSpool } from "@belvador/spool";

Sentry.init({
  dsn: process.env.BELVADOR_DSN ?? "https://<public-key>@ingest.belvador.dev/<project-id>",
  release: process.env.APP_VERSION,
  environment: process.env.NODE_ENV,
  sendDefaultPii: false,
  tracesSampleRate: 0,
  maxBreadcrumbs: 50,
  maxValueLength: 8192,
  // Belvador's scrubbing: request bodies, cookies and credentials never leave the app.
  beforeSend: scrubEvent,
  beforeBreadcrumb: scrubBreadcrumb,
  // Errors that cannot be sent wait on disk and go out after a restart.
  transport: withSpool(Sentry.makeFetchTransport, {
    dir: process.env.BELVADOR_SPOOL_DIR ?? "./.belvador-spool",
  }),
});
```

```bash
bun --preload ./instrument.ts server.ts
```

Verify
Start the app locally with BELVADOR_DSN set and trigger one test error (a route or a one-off script that throws). Confirm it appears as an issue in the Belvador project. Do not send test errors to production unless the user says so.

Report
List the files you changed, the packages added with exact versions, the environment variables the app now needs, the test error you saw arrive, and anything you could not verify.
````

## Limits to know

- An uncaught error still ends the process, as Bun does without an SDK; the error is sent first.
- An error thrown in a `Bun.serve` handler is reported and answered with a 500; the server keeps answering. Bun 1.4.4 still ends such a process with exit code 1 when it stops — with or without an SDK — so a process manager may count it as a failure.
- Belvador has not checked Bun framework integrations (Elysia, Hono) one by one; errors they turn into responses themselves need `Sentry.captureException`.
- Keep `tracesSampleRate: 0`; for traces use [OpenTelemetry](https://belvador.dev/en/docs/otel/overview).
