Skip to main content
Claude Code reports every session over OpenTelemetry. The LangWatch command-line tool connects that report to your workspace, so you can read the cost of each session, the tool call tree of each turn, and the prompt and the response behind both. There are two ways to set it up:
  • langwatch claude starts Claude Code and sets up the connection on the first run. Use it on a machine you work on.
  • langwatch instrument claude writes the same setup and exits. Use it on a server, or when the telemetry must go to a team project.

Before you start

  • Install Claude Code.
  • Install the LangWatch command-line tool: npm install -g langwatch
  • Create an account at app.langwatch.ai, or run your own instance with npx @langwatch/server.

Run Claude Code through the wrapper

The first run asks one question: LangWatch remembers the answer, so later runs do not ask again. Everything else is unchanged: langwatch claude accepts the same flags, the same subcommands and the same keyboard shortcuts as claude, and it returns the same exit code.
A virtual key is created the first time you choose the gateway, not at login. If you always keep your own subscription, LangWatch never creates one for you.

Set up a machine without starting Claude Code

langwatch instrument claude writes the connection into ~/.claude/settings.json and exits. A plain claude run on that machine then sends its telemetry to LangWatch. This is the setup for a server that holds many agents, for a machine image, or for a configuration management run.

Choose where the telemetry goes

Pass one scope flag, and one only: The wrapper accepts the same flags: langwatch claude --project acme-app pins the project and then starts Claude Code. --project and --personal are LangWatch flags; LangWatch removes them from the command line before Claude Code reads it.

Remove the setup

This deletes every connection the command-line tool wrote, for every tool.

What you get in LangWatch

Open your personal page at /me, or open the project you pinned Claude Code to.
  • Sessions: one row per Claude Code session, with the context it carried, how often it compacted, how long it worked, and a terminal replay of the whole conversation.
  • Traces: one trace per turn, with the tool call tree, the latency of each step, and each subagent on its own branch.
  • Cost: the amount per session and per turn, the model of each turn, and the split between tokens written to the prompt cache and tokens read back from it.
  • Pull requests: what each pull request cost across every session that drove it.
  • Content: the prompt of each turn, the input and the output of each tool call, and the response of the model. Content is what a data privacy policy can drop or restrict, so the terminal replay and these fields are only as complete as that policy allows.
Your agent can read all of this back. See Explore your usage with your own agent.

Attribute sessions to pull requests

The session reports the repository and branch it works on: automatically from its working directory, and by declaring with langwatch ingest context when it works somewhere else through the Bash tool. The setup installs the declaration channel for you, through the LangWatch plugin or the hooks langwatch instrument claude writes. Repository and PR attribution covers both channels and their limits.

Content and privacy

The setup that langwatch claude and langwatch instrument claude write turns on the four Claude Code content flags, because the content is what makes a trace worth reading: Remove the flags from ~/.claude/settings.json to keep only the usage numbers: tokens, cost, model, latency, the tool call tree and the subagent tree stay, and the content stops. langwatch claude and langwatch instrument claude set OTEL_LOG_RAW_API_BODIES, which already carries the assistant’s response text along with the rest of the request and response body. If you want the response text on its own, without the full body, add OTEL_LOG_ASSISTANT_RESPONSES yourself: the wrapper does not set it for you. Claude Code shortens each inline body at 60 kilobytes. CLAUDE_CODE_OTEL_CONTENT_MAX_LENGTH raises that limit. Claude Code always removes extended thinking from the bodies, so LangWatch never receives it.

Route through the LangWatch AI Gateway

Everything above is telemetry: Claude Code keeps your own plan and your own provider login. Choosing Using an API key on the first wrapper run instead routes the model calls themselves through the LangWatch AI Gateway with a virtual key, which adds budgets, model policies and provider fallback on top of the same telemetry. Claude Code through the gateway covers that setup, model aliases and the governance recipes.

Troubleshooting

Restart Claude Code. It reads ~/.claude/settings.json at start, so a session that was already open when you ran langwatch instrument claude keeps its old configuration.Then run langwatch instrument claude again and read what it prints. It names the file it wrote and the destination the telemetry goes to.
CLAUDE_CODE_ENHANCED_TELEMETRY_BETA is missing. It is the flag that turns spans on, and OTEL_TRACES_EXPORTER has no effect without it. Cost and tokens still arrive, so the session appears, but LangWatch has no structure to attach them to and every subagent folds into the main session.
OTEL_LOG_USER_PROMPTS is missing. Claude Code removes the prompt text from its user_prompt event by default.
Both OTEL_LOG_RAW_API_BODIES and OTEL_LOG_ASSISTANT_RESPONSES are missing. Claude Code reports the response text of the assistant on its api_response_body event (needs OTEL_LOG_RAW_API_BODIES=1, and carries the full request and response JSON) or on its lighter assistant_response event (needs OTEL_LOG_ASSISTANT_RESPONSES=1, and carries only the response text). Both are off by default. Set OTEL_LOG_ASSISTANT_RESPONSES=1 if you want the response without the rest of the body.Claude Code docs describe OTEL_LOG_ASSISTANT_RESPONSES as falling back to the value of OTEL_LOG_USER_PROMPTS. Don’t rely on that fallback: set it explicitly.Claude Code also reports api_response_body and assistant_response for work that is not a conversation, such as an autosuggestion or a session title. LangWatch filters those out, so a title never becomes the output of a trace.
Run langwatch instrument claude --project <id-or-slug> to move it to a team project, or langwatch instrument claude --personal to move it back to your own.
Check that the key is live and that the header reads Authorization=Bearer <key>. An ingest key can write traces and no other requests, so a key that works for ingestion still fails on other endpoints.On a machine with a login, run langwatch login --device again to refresh the session.
The model name on the span does not match an entry in the LangWatch cost catalog. Read gen_ai.request.model on the span. Current names such as claude-sonnet-4-6, claude-opus-4-7 and claude-haiku-4-5 match; older aliases return no cost.
For more failure modes see CLI debug.

Manual OpenTelemetry setup

Use this when you cannot install the LangWatch command-line tool. The block below mirrors the variables langwatch instrument claude writes for you, plus the optional OTEL_LOG_ASSISTANT_RESPONSES flag, which the wrapper does not set. Drop that line if you only want what the wrapper gives you.

Environment variables

The header takes an ingest key (ik-lw-…), which can create traces and no other requests. Keep a full project API key (sk-lw-…) out of the configuration of a coding agent: it can read and change your project data, and a coding agent has no use for that. For a self-hosted instance, replace the endpoint with the address of your instance followed by /api/otel. Claude Code appends /v1/traces, /v1/logs and /v1/metrics on its own. Never write the per-signal path yourself.
The five OTEL_LOG_* flags put your session content in LangWatch. Read Content and privacy before you use them where the content matters.

Settings file

Claude Code reads the same variables from the env block of ~/.claude/settings.json, which survives a new terminal:
To wire one repository instead of the whole machine, see Per-repository telemetry below. To apply the block to every machine of a fleet through managed settings, see Headless machines and CI.

Per-repository telemetry

Use this when work and personal repositories share a machine and you want telemetry on for some of them only. Claude Code walks up from the launch directory to find .claude/settings.json, so the wiring travels with the repository rather than with your account. Split it across two files. The committed one decides the shape of the export: .claude/settings.json (commit this):
.claude/settings.local.json (add it to .gitignore) holds the key, the enable flag and every content flag, so each person turns content on for themselves:
Telemetry stays off until CLAUDE_CODE_ENABLE_TELEMETRY is set, so a fresh clone that carries only the committed file sends no telemetry.
OTEL_RESOURCE_ATTRIBUTES is the one key that does not merge across the two files. The local file replaces the committed value outright, so carry every attribute you want in the local file or the missing ones are dropped from every span.
Two things catch people out. Claude Code reads the env block at launch, so a session that was already running keeps sending no telemetry until you restart it. And a .gitignore that covers all of .claude/ stops git add from staging the committed file, so your own telemetry works while your teammates inherit none of it. A bare !.claude/settings.json negation does not fix that, because git does not descend into an excluded directory:

Team attribution

Add your own labels to every trace with OTEL_RESOURCE_ATTRIBUTES:
OTEL_RESOURCE_ATTRIBUTES follows the W3C Baggage specification. Write comma-separated key=value pairs. Values cannot contain a space, a double quote, a comma, a semicolon or a backslash. Percent-encode any other character outside that range.

Headers from a script

An enterprise that issues short-lived tokens can produce the headers at start time. Add this to .claude/settings.json:
The script writes a JSON object of string headers to standard output:
Claude Code reads the headers at start only, so a token that expires mid-session is not refreshed. For frequent refresh, send the telemetry to an OpenTelemetry Collector and let the collector refresh its own headers.
Last modified on August 27, 2026