claude code plugin that sends telemetry to otlp destinations (traces)
0
fork

Configure Feed

Select the types of activity you want to include in your feed.

JavaScript 98.5%
TypeScript 1.5%
5 1 0

Clone this repository

https://tangled.org/aparker.io/otel-claude-code-hooks https://tangled.org/did:plc:gttrfs4hfmrclyxvwkwcgpj7/otel-claude-code-hooks
git@tangled.org:aparker.io/otel-claude-code-hooks git@tangled.org:did:plc:gttrfs4hfmrclyxvwkwcgpj7/otel-claude-code-hooks

For self-hosted knots, clone URLs may differ based on your setup.

Download tar.gz
README.md

OpenTelemetry Tracing Plugin for Claude Code#

A Claude Code plugin that traces conversations, tool calls, subagent executions, and context compaction via OpenTelemetry OTLP to any compatible backend — Honeycomb, Jaeger, Grafana, Datadog, or anything else that speaks OTLP.

Prerequisites#

Installation#

From source#

pnpm install
pnpm build
claude --plugin-dir /path/to/otel-claude-code-hooks

Setting environment variables#

Option 1: Claude Code settings file (recommended)

Add to ~/.claude/settings.json:

{
  "env": {
    "CC_OTEL_ENABLED": "true",
    "OTEL_EXPORTER_OTLP_ENDPOINT": "https://api.honeycomb.io",
    "OTEL_EXPORTER_OTLP_HEADERS": "x-honeycomb-team=YOUR_API_KEY",
    "OTEL_SERVICE_NAME": "claude-code"
  }
}

Option 2: Export to shell

Add to your ~/.zshrc, ~/.bashrc, or ~/.bash_profile:

export CC_OTEL_ENABLED="true"
export OTEL_EXPORTER_OTLP_ENDPOINT="https://api.honeycomb.io"
export OTEL_EXPORTER_OTLP_HEADERS="x-honeycomb-team=YOUR_API_KEY"
export OTEL_SERVICE_NAME="claude-code"

Backend examples#

Honeycomb

export OTEL_EXPORTER_OTLP_ENDPOINT="https://api.honeycomb.io"
export OTEL_EXPORTER_OTLP_HEADERS="x-honeycomb-team=YOUR_API_KEY"

Jaeger (local)

export OTEL_EXPORTER_OTLP_ENDPOINT="http://localhost:4318"

Grafana Cloud

export OTEL_EXPORTER_OTLP_ENDPOINT="https://otlp-gateway-prod-us-central-0.grafana.net/otlp"
export OTEL_EXPORTER_OTLP_HEADERS="Authorization=Basic BASE64_ENCODED_CREDENTIALS"

What gets traced#

The plugin creates a span hierarchy for each conversation turn:

Claude Code Turn (SERVER)
├── chat claude-sonnet-4-5 (CLIENT)   — LLM call with gen_ai.* attributes
├── Read (INTERNAL)                    — tool execution
├── Edit (INTERNAL)                    — tool execution
├── chat claude-sonnet-4-5 (CLIENT)   — next LLM call
└── Bash (INTERNAL)                    — tool execution

LLM spans include Gen AI semantic convention attributes:

  • gen_ai.system: "anthropic"
  • gen_ai.operation.name: "chat"
  • gen_ai.request.model / gen_ai.response.model
  • gen_ai.usage.input_tokens / gen_ai.usage.output_tokens
  • gen_ai.usage.cache_read_input_tokens / gen_ai.usage.cache_creation_input_tokens
  • gen_ai.response.finish_reasons

Tool spans include gen_ai.tool.name.

Subagent runs are nested under their parent Agent tool span as a chain of sub-turns.

Context compaction events are traced with trigger type and summary.

Interrupted turns are marked with SpanStatusCode.ERROR and message "Interrupted".

Environment variables#

Variable Required Default Description
CC_OTEL_ENABLED Yes Set to "true" to enable tracing
OTEL_EXPORTER_OTLP_ENDPOINT No http://localhost:4318 OTLP endpoint URL
OTEL_EXPORTER_OTLP_TRACES_ENDPOINT No Overrides the generic endpoint for traces only
OTEL_EXPORTER_OTLP_HEADERS No Comma-separated key=value pairs (e.g., x-honeycomb-team=KEY)
OTEL_EXPORTER_OTLP_TRACES_HEADERS No Overrides the generic headers for traces only
OTEL_SERVICE_NAME No "claude-code" service.name resource attribute
CC_OTEL_DEBUG No "false" Enable debug logging to ~/.claude/state/hook.log
CC_OTEL_TRACEPARENT No W3C traceparent to nest traces under an external parent

Nesting traces under an existing span#

Set CC_OTEL_TRACEPARENT to a W3C traceparent string to nest all Claude Code traces as children of an existing span. This is useful when Claude Code is invoked programmatically as part of a larger traced workflow.

import subprocess
from opentelemetry import trace

tracer = trace.get_tracer(__name__)

with tracer.start_as_current_span("my-workflow") as span:
    ctx = span.get_span_context()
    traceparent = f"00-{format(ctx.trace_id, '032x')}-{format(ctx.span_id, '016x')}-01"
    subprocess.run(
        ["claude", "-p", prompt],
        env={
            **os.environ,
            "CC_OTEL_ENABLED": "true",
            "CC_OTEL_TRACEPARENT": traceparent,
        },
    )

The resulting trace hierarchy:

my-workflow (your app)
└── Claude Code Turn 1 (SERVER)
    ├── chat claude-sonnet-4-5 (CLIENT)
    ├── Read (INTERNAL)
    └── chat claude-sonnet-4-5 (CLIENT)

Architecture#

The plugin uses 9 Claude Code hooks, each running as a separate Node.js process:

Hook Purpose
UserPromptSubmit Generates trace context (traceId + turnSpanId), stores in state
PreToolUse Records tool start timestamp
PostToolUse Stores agent_id mapping for subagent linking
Stop Main hook — reads transcript, creates all spans, flushes via OTLP
StopFailure Creates Turn span with error status on API failures
SubagentStop Queues subagent info for Stop to process
PreCompact / PostCompact Traces context compaction events
SessionEnd Closes interrupted turns on exit

Cross-process span IDs are handled by a ControlledIdGenerator that implements the OTel SDK's IdGenerator interface. Hooks pre-generate trace/span IDs and store them in a shared state file (~/.claude/state/otel_tracing_state.json) with file locking for concurrency safety.

The plugin uses the real OTel SDK stack:

  • @opentelemetry/sdk-trace-baseBasicTracerProvider, BatchSpanProcessor
  • @opentelemetry/exporter-trace-otlp-httpOTLPTraceExporter
  • @opentelemetry/semantic-conventions — Gen AI and service attribute constants

Known limitations#

  • Subagents are only traced upon completion. If you interrupt during a subagent run, that subagent's traces will be lost.
  • Sessions older than 24 hours are pruned from state. Resuming after that starts fresh.

Development#

pnpm install
pnpm dev         # Watch mode — recompiles on changes
pnpm test        # Run tests
pnpm build       # Production build (tsc + esbuild bundle)

After making changes, run pnpm build and send a new message in Claude Code to pick up the updated hooks.

License#

MIT