# Claude Code

Instrument Claude Code with Brizz using settings.json or environment variables.

Claude Code emits OpenTelemetry natively. Point it at Brizz and every run your team does lands in
the same session view, cost accounting, and issue detection Brizz gives your production agents.

## What you get

- **Every run as a session.** The prompts, the replies, and the full tool tree underneath them, in
  the same transcript view as the rest of your agents.
- **Cost and token accounting.** Per session, per model, per person — so "what is Claude Code
  costing us" is a number rather than a monthly invoice line.
- **Tool behaviour.** Which tools get used, how often they fail, and how long they take.
- **Failure patterns across runs.** Brizz clusters what goes wrong, so a problem hitting the whole
  team reads as one issue rather than as everyone's individual bad afternoon.

The setup below captures full content — prompts, replies, and tool inputs. To capture less, see
[Advanced](#advanced).

## Before you start

You need a **Server DSN**. Create one under **Organization Settings → API Keys → Create Telemetry
API Key → Server DSN** (see [Server DSN](/docs/admin/server-dsn.md)), entering the service name you
want this agent to report as. You get back one string:

```
https://<credential>@<ingest-host>/<service-name>
```

Every step below uses its three parts:

| Part | Example | Where it goes |
| --- | --- | --- |
| `<credential>` | `brz_live_a1b2c3…` | The `Authorization: Bearer` header |
| `<ingest-host>` | `ingest.brizz.ai` | The OTLP endpoint, as `https://<ingest-host>` |
| `<service-name>` | `claude-code` | The `X-Brizz-Service-Name` header |

## Configure Claude Code

:::tabs
:::tab[settings.json]
Claude Code applies the `env` block of its settings file to every session, so this survives new
terminals, new machines, and IDE-launched sessions.

Put it in `~/.claude/settings.json` for yourself, or `.claude/settings.local.json` inside a project
to scope it to that repo:

```json
{
  "env": {
    "CLAUDE_CODE_ENABLE_TELEMETRY": "1",
    "CLAUDE_CODE_ENHANCED_TELEMETRY_BETA": "1",
    "OTEL_TRACES_EXPORTER": "otlp",
    "OTEL_LOGS_EXPORTER": "otlp",
    "OTEL_EXPORTER_OTLP_PROTOCOL": "http/protobuf",
    "OTEL_EXPORTER_OTLP_ENDPOINT": "https://<ingest-host>",
    "OTEL_EXPORTER_OTLP_HEADERS": "Authorization=Bearer <credential>,X-Brizz-Service-Name=<service-name>",
    "OTEL_LOG_USER_PROMPTS": "1",
    "OTEL_LOG_ASSISTANT_RESPONSES": "1",
    "OTEL_LOG_TOOL_CONTENT": "1",
    "OTEL_LOG_TOOL_DETAILS": "1"
  }
}
```

If the file already exists, merge these keys into its `env` block rather than replacing the file.

:::tab[Environment variables]
Paste your DSN into the first line and let the block split it — nothing below that line needs
editing. Works in `bash` and `zsh`, and handles a credential containing an `@` or a host carrying a
port:

```bash
export BRIZZ_DSN="https://<credential>@<ingest-host>/<service-name>"

_brizz="${BRIZZ_DSN#*://}"                            # <credential>@<ingest-host>/<service-name>
_brizz_hostpath="${_brizz##*@}"                       # <ingest-host>/<service-name>
export BRIZZ_CREDENTIAL="${_brizz%@*}"
export BRIZZ_INGEST_HOST="${_brizz_hostpath%%/*}"
export BRIZZ_SERVICE_NAME="${_brizz_hostpath#*/}"
unset _brizz _brizz_hostpath

export CLAUDE_CODE_ENABLE_TELEMETRY=1
export CLAUDE_CODE_ENHANCED_TELEMETRY_BETA=1
export OTEL_TRACES_EXPORTER=otlp
export OTEL_LOGS_EXPORTER=otlp
export OTEL_EXPORTER_OTLP_PROTOCOL=http/protobuf
export OTEL_EXPORTER_OTLP_ENDPOINT="https://${BRIZZ_INGEST_HOST}"
export OTEL_EXPORTER_OTLP_HEADERS="Authorization=Bearer ${BRIZZ_CREDENTIAL},X-Brizz-Service-Name=${BRIZZ_SERVICE_NAME}"

export OTEL_LOG_USER_PROMPTS=1
export OTEL_LOG_ASSISTANT_RESPONSES=1
export OTEL_LOG_TOOL_CONTENT=1
export OTEL_LOG_TOOL_DETAILS=1
```

To check the split before running anything:

```bash
echo "host=$BRIZZ_INGEST_HOST service=$BRIZZ_SERVICE_NAME"
```

The variables apply to whatever process you launch, so pick the scope you want: paste the block into
one terminal for a single session, add it to `~/.zshrc` or `~/.bashrc` for every run, or keep it in
a `.env` you source per project.
:::

:::warning Don't commit the credential
A Server DSN is a write credential. `.claude/settings.json` is checked into the repository — keep
the block in `.claude/settings.local.json` or in your user-level `~/.claude/settings.json` instead.
:::

:::warning Keep the credential out of the URL
`OTEL_EXPORTER_OTLP_ENDPOINT` is the host on its own — no credential, no path. Pasting the whole DSN
there is the most common mistake: the credential is dropped, nothing authenticates, and Claude Code
reports no error.
:::

:::info The service name comes from the header
`X-Brizz-Service-Name` is the service you'll pick in the dashboard, and it's required alongside the
credential. Give each machine or team the same name to see their runs together, or different names
to keep them apart. `OTEL_SERVICE_NAME` has no effect here — the header wins.
:::

## Verify

Run `claude`, ask it something, and open the service you named in the dashboard. The session appears
within a few seconds of the run finishing.

If nothing arrives, see [Troubleshooting](#troubleshooting).

## Advanced

### Capture less content

The setup above sends prompt text, reply text, and tool inputs. Remove any of these to narrow what
leaves your machine — each one defaults to off, so dropping the line is enough:

| Setting | What it adds | Remove it and you keep |
| --- | --- | --- |
| `OTEL_LOG_USER_PROMPTS` | The text of your prompts | Turn counts, timing, cost |
| `OTEL_LOG_ASSISTANT_RESPONSES` | The text of Claude's replies | Turn counts, timing, cost |
| `OTEL_LOG_TOOL_CONTENT` | `tool_input` — the raw arguments a tool was called with, e.g. `{"command":"echo hi"}` — and `tool_parameters`, a structured breakdown of the same call | Tool name, ids, success flag, duration, byte sizes |
| `OTEL_LOG_TOOL_DETAILS` | Per-tool call details | Tool name, ids, success flag, duration, byte sizes |

Removing all four leaves Brizz the shape of every session — turns, tool calls, tokens, cost — and
none of what was said.

Tool **output is never emitted**, with or without these settings; only its size in bytes is
reported. So tool content reveals what was run and with what arguments, never what came back.

### Cap content size

`CLAUDE_CODE_OTEL_CONTENT_MAX_LENGTH` truncates emitted content at a character count — useful when
prompts or tool arguments are large.

### Raw API bodies

`OTEL_LOG_RAW_API_BODIES=1` adds the full request and response bodies of every model call. This is
off in the setup above: the bodies are large and contain everything sent to the model, including
context Brizz already captures in structured form. Turn it on only when you're debugging the API
layer itself.

### Short runs that never appear

The exporter flushes on an interval, and a quick one-off run can exit before the next flush. If
short runs are missing but longer sessions arrive, shorten the intervals (milliseconds):

```bash
export OTEL_TRACES_EXPORT_INTERVAL=2000  # spans, default 5000
export OTEL_LOGS_EXPORT_INTERVAL=2000    # logs, default 5000
```

## Troubleshooting

Claude Code doesn't surface export failures, so a run that's exporting nothing looks exactly like a
working one. Send one request yourself with the same values — the status code says which it is:

```bash
curl -i -X POST "https://<ingest-host>/v1/traces" \
  -H "Authorization: Bearer <credential>" \
  -H "X-Brizz-Service-Name: <service-name>" \
  -H "Content-Type: application/json" \
  -d '{"resourceSpans":[]}'
```

| Response | What it means |
| --- | --- |
| `200` | Brizz is accepting your telemetry. If runs still don't appear, check `CLAUDE_CODE_ENABLE_TELEMETRY` is set in the settings file or shell you actually ran `claude` from, and that you're looking at the right service in the dashboard |
| `400` | `X-Brizz-Service-Name` is missing or empty — it's required alongside the credential |
| `401` | The credential is wrong, or it isn't being sent as `Authorization=Bearer`. Check you didn't leave it in the endpoint URL |
| `404` | The endpoint has a path on it. It should be the bare host — the exporter appends `/v1/traces` and `/v1/logs` itself |
| `403` | Your plan's usage allowance is spent. Telemetry sent while you're paused isn't recorded or backfilled — see [Plan & billing](/docs/admin/plan-and-billing.md) |

If the curl returns `200` and long sessions arrive but short ones don't, that's the
[export intervals](#short-runs-that-never-appear), not your credential.

## See also

- [Server DSN](/docs/admin/server-dsn.md) — create and rotate the credential this page uses.
- [Telemetry ingestion API](/docs/api/telemetry.md) — the endpoint Claude Code exports to.
- [PII & privacy](/docs/instrument/pii-and-privacy.md) — what to keep out of telemetry.
- [Plan & billing](/docs/admin/plan-and-billing.md) — your usage allowance and what happens at the limit.

---

[All Brizz documentation](https://docs.brizz.ai/llms.txt) · [Full documentation (single file)](https://docs.brizz.ai/llms-full.txt)
