Setting up the OTel stack
Catalyst uses claude-code-otel to export OpenTelemetry telemetry (events, metrics, logs) from every Claude Code session — including workers dispatched by /catalyst-dev:orchestrate.
This page covers setting up the stack itself. For making sure agents record data correctly once the stack is running, see Recording agent data.
What Gets Exported
Section titled “What Gets Exported”When instrumentation is enabled, each Claude Code session emits:
| Telemetry | Examples |
|---|---|
| Events | session_start, tool_use, tool_error, subagent_spawn, stop |
| Metrics | Tokens consumed, cache hit rate, cost, tool call counts, duration per phase |
| Logs | Stderr/stdout of shell tools, agent final messages |
| Resource attributes | service.name, session.id, user.id, project.key, orchestrator.id, worker.ticket |
The resource attributes are what make multi-worker orchestration queryable — you can filter a Grafana dashboard to one orchestrator’s wave and see all its workers side-by-side.
Stack Components
Section titled “Stack Components”The reference stack runs entirely locally via docker-compose:
Claude Code (OTLP HTTP export) ──> OTel Collector ──┬──> Prometheus ──> Grafana (metrics) ├──> Loki ──> Grafana (logs) └──> Tempo ──> Grafana (traces)For production use, point the OTel Collector at your hosted backend (Honeycomb, Datadog, Grafana Cloud, etc.) instead of the local Prometheus/Loki/Tempo.
Installation
Section titled “Installation”1. Clone and start the stack
Section titled “1. Clone and start the stack”git clone https://github.com/coalesce-labs/claude-code-otel.gitcd claude-code-oteldocker compose up -dThis starts:
- OTel Collector on
localhost:4318(OTLP/HTTP) - Prometheus on
localhost:9090 - Loki on
localhost:3100 - Tempo on
localhost:3200 - Grafana on
localhost:3000(admin / admin)
2. Configure Claude Code
Section titled “2. Configure Claude Code”Set environment variables in your shell profile (.zshrc, .bashrc, or a tool like direnv):
export CLAUDE_CODE_ENABLE_TELEMETRY=1export OTEL_EXPORTER_OTLP_ENDPOINT=http://localhost:4318export OTEL_METRICS_EXPORTER=otlpexport OTEL_LOGS_EXPORTER=otlpFor session-scoped enablement (only when running orchestration), set them in the orchestrator’s launch command instead — see the shell wrapper section in Recording agent data.
3. Verify ingestion
Section titled “3. Verify ingestion”Start any Claude Code session, then check the OTel collector’s logs:
docker compose logs -f otel-collectorYou should see batches of telemetry arriving within ~10 seconds of the first tool use. If not, see Troubleshooting below.
4. Import the Grafana dashboard
Section titled “4. Import the Grafana dashboard”The claude-code-otel repo ships with a pre-built Grafana dashboard at dashboards/claude-code.json. Import it via Grafana → Dashboards → New → Import → Upload JSON file.
The dashboard includes:
- Session overview — active sessions, tokens/min, cache hit rate
- Per-project breakdown — tokens by
project.keyresource attribute - Orchestrator view — filtered by
orchestrator.id, shows all workers in one wave - Tool use frequency — which tools agents reach for most often
- Cost tracking — $ per session, per worker, per orchestrator
Troubleshooting
Section titled “Troubleshooting”No telemetry arriving
Section titled “No telemetry arriving”# 1. Confirm env vars are set in the Claude Code processclaude config --debug # Look for CLAUDE_CODE_ENABLE_TELEMETRY=1
# 2. Confirm the collector is reachablecurl -v http://localhost:4318/v1/metrics# Expect: 405 Method Not Allowed (because GET, but proves the port is open)
# 3. Check collector logs for rejected batchesdocker compose logs otel-collector | grep -i errorResource attributes missing
Section titled “Resource attributes missing”Claude Code sets service.name=claude-code automatically, but Catalyst-specific attributes (orchestrator.id, worker.ticket, project.key) are set by the shell wrapper — not by Claude Code itself. See Recording agent data.
Dashboard empty after import
Section titled “Dashboard empty after import”Grafana’s Prometheus data source defaults may not match the docker-compose stack’s port. In Grafana → Connections → Data sources → Prometheus, set the URL to http://prometheus:9090 (service name, not localhost, since Grafana runs inside the docker network).
Opting out
Section titled “Opting out”To disable telemetry for a single session without unsetting env vars, run:
CLAUDE_CODE_ENABLE_TELEMETRY=0 claudeTo disable globally, remove the env vars from your shell profile.