ai-ml

Claude Agent SDK Observability With OpenTelemetry (2026)

July 15, 2026

Claude Agent SDK Observability With OpenTelemetry (2026)

The Claude Agent SDK can export every model request, tool call, and hook execution in an agent run as OpenTelemetry traces — but tracing is off by default and gated behind a beta flag, and the span tree it produces has its own naming scheme that only partially overlaps with the OpenTelemetry GenAI semantic conventions12. This tutorial turns tracing on in TypeScript, reads the real span hierarchy, and wires it to a backend you can actually look at.

TL;DR

Set CLAUDE_CODE_ENABLE_TELEMETRY=1 and CLAUDE_CODE_ENHANCED_TELEMETRY_BETA=1, point OTEL_TRACES_EXPORTER=otlp at a collector endpoint, and every agent turn becomes a claude_code.interaction root span with child spans for each model request (claude_code.llm_request), each tool call (claude_code.tool, with .blocked_on_user and .execution sub-spans), and each hook (claude_code.hook, beta-gated separately)12. Five attributes on those spans — gen_ai.system, gen_ai.request.model, gen_ai.response.id, gen_ai.response.finish_reasons, and gen_ai.tool.call.id — are explicitly documented as OpenTelemetry GenAI semantic convention attributes; everything else, including the span names themselves, is Anthropic's own claude_code.* namespace2. Runtime: Node 18+ (built and type-checked against Node 22.22.3). SDK: @anthropic-ai/claude-agent-sdk@0.3.2103. Build time: 25-30 minutes.

What you'll learn

  • Which environment variables turn on traces, metrics, and log events independently, and why traces need one flag the other two signals don't1
  • The exact span hierarchy the Agent SDK emits, down to the attribute level, and which attributes only appear when you opt in to capturing content2
  • How to build a minimal tool-calling agent worth tracing, and point its telemetry at a local receiver you can actually read
  • How to make the Claude Agent SDK's claude_code.interaction span show up as a child of your own application's trace, instead of a disconnected root
  • Whether Claude Agent SDK tracing actually follows the OpenTelemetry GenAI semantic conventions, and what that distinction means for your dashboards
  • How to get per-request cost and latency two different ways — one that needs no OpenTelemetry at all — and when to reach for each

Prerequisites

  • Node.js 18.0.0 or later — the SDK's package.json still sets "engines": {"node": ">=18.0.0"} at the current version; this tutorial was built and type-checked against Node 22.22.33
  • @anthropic-ai/claude-agent-sdk@0.3.210 (the registry-confirmed latest as of this writing, published 2026-07-14), plus peer dependencies zod@^4.0.0, @anthropic-ai/sdk@>=0.93.0, and @modelcontextprotocol/sdk@^1.29.03
  • TypeScript 5.x or later with strict enabled (this tutorial's code was type-checked with TypeScript 7.0.2, the latest tag on npm as of this writing)
  • An ANTHROPIC_API_KEY if you want to run the agent loop end to end and see real spans arrive at a collector — the code below is type-checked against the real SDK, but this automated writing pipeline doesn't hold a paid API key, so live model calls were not executed; see Verification for exactly what was and wasn't run

Why doesn't total_cost_usd already solve this?

The Agent SDK's response stream already includes a result message with total_cost_usd, duration_ms, and token usage for the whole query() call4. That's enough to log "this run cost $0.03." It is not enough to answer "which single tool call in that run accounted for most of the latency," "did the model have to retry before a particular tool call succeeded," or "is one specific MCP server's tool consistently the slow part of every run." Those questions need a span per step, not one aggregate number per run — which is exactly what OpenTelemetry tracing adds on top of the result message, not instead of it.

Step 1: Set up the project

mkdir agent-observability && cd agent-observability
npm init -y
npm install @anthropic-ai/claude-agent-sdk@0.3.210 zod@4
npm install -D typescript@7 @types/node@26
npx tsc --init --target ES2022 --module NodeNext --moduleResolution NodeNext --strict

Add "type": "module" to package.json — the SDK itself ships as "type": "module", and tsc --moduleResolution NodeNext expects the consuming project to match3.

Step 2: Turn on the three telemetry signals

Metrics, log events, and traces are three independent signals, each with its own exporter switch, and all three are off until you flip the master switch1:

// otel-env.ts
export const otelEnv = {
  ...process.env,
  // Master switch — nothing exports without this
  CLAUDE_CODE_ENABLE_TELEMETRY: "1",
  // Traces specifically are beta and need this second flag;
  // metrics and log events do not.
  CLAUDE_CODE_ENHANCED_TELEMETRY_BETA: "1",
  OTEL_TRACES_EXPORTER: "otlp",
  OTEL_METRICS_EXPORTER: "otlp",
  OTEL_LOGS_EXPORTER: "otlp",
  OTEL_EXPORTER_OTLP_PROTOCOL: "http/json",
  OTEL_EXPORTER_OTLP_ENDPOINT: "http://localhost:4318",
  OTEL_SERVICE_NAME: "weather-agent-demo",
};

Two things that are easy to get wrong here. First, never set console as an exporter value when running through the SDK — the SDK uses stdout as its own message channel, so a console exporter's output gets interleaved with (and can corrupt) the protocol it's reading1. Point OTEL_EXPORTER_OTLP_ENDPOINT at a real local receiver instead, which is exactly what Step 5 builds. Second, in TypeScript, whatever you pass as options.env replaces the subprocess's environment entirely rather than merging with it — the installed SDK's own type definition spells this out verbatim: "this value REPLACES the subprocess environment entirely — it is not merged with process.env"4. That's why otelEnv above spreads ...process.env first; skip that and the child process loses PATH, HOME, and ANTHROPIC_API_KEY. Python's ClaudeAgentOptions.env behaves differently — it merges on top of the inherited environment instead of replacing it1.

Step 3: Give the agent a tool worth tracing

A single-turn "hello world" query only produces one llm_request span. To see the full hierarchy — including tool spans — the agent needs at least one tool call. This is a minimal weather tool built with the SDK's own tool() and createSdkMcpServer() helpers:

// weather-tool.ts
import { tool, createSdkMcpServer } from "@anthropic-ai/claude-agent-sdk";
import { z } from "zod";

const getWeather = tool(
  "get_weather",
  "Gets the current weather for a given city",
  { city: z.string() },
  async (args) => {
    const weatherData: Record<string, string> = {
      Berlin: "Cloudy, 15°C",
      "New York": "Sunny, 24°C",
    };
    const weather = weatherData[args.city] ?? "Weather data not available";
    return {
      content: [{ type: "text", text: `Weather in ${args.city}: ${weather}` }],
    };
  }
);

export const weatherServer = createSdkMcpServer({
  name: "weather",
  version: "1.0.0",
  tools: [getWeather],
});

Wire it into a query() call alongside the telemetry environment from Step 2:

// index.ts
import { query } from "@anthropic-ai/claude-agent-sdk";
import { otelEnv } from "./otel-env.js";
import { weatherServer } from "./weather-tool.js";

async function main() {
  for await (const message of query({
    prompt: "What's the weather like in Berlin?",
    options: {
      model: "claude-opus-4-8",
      mcpServers: { weather: weatherServer },
      allowedTools: ["mcp__weather__get_weather"],
      env: otelEnv,
    },
  })) {
    if (message.type === "assistant") {
      console.log(message.message.content);
    }
    if (message.type === "result" && message.subtype === "success") {
      console.log("total_cost_usd:", message.total_cost_usd);
      console.log("duration_ms:", message.duration_ms);
    }
  }
}

main();

This runs on Claude Opus 4.8 (claude-opus-4-8) — Anthropic's own guidance says to "start with Claude Opus 4.8 for complex agentic coding and enterprise work" if you're unsure which model to use5.

Step 4: Read the span tree

With tracing on, one user turn produces this hierarchy — this is the documented tree, reproduced verbatim from the monitoring reference2:

claude_code.interaction
├── claude_code.llm_request
├── claude_code.hook                    (requires detailed beta tracing)
└── claude_code.tool
    ├── claude_code.tool.blocked_on_user
    ├── claude_code.tool.execution
    └── (Agent tool) subagent claude_code.llm_request / claude_code.tool spans

claude_code.interaction is the root: one per user turn, carrying user_prompt_length, interaction.sequence, and interaction.duration_ms. claude_code.llm_request wraps each call to the Claude API and is where cost- and latency-relevant attributes live: model, duration_ms, ttft_ms (time to first token), input_tokens, output_tokens, cache_read_tokens, cache_creation_tokens, stop_reason, and success2. claude_code.tool wraps each tool invocation with tool_name, duration_ms, and result_tokens, and splits into two children: claude_code.tool.blocked_on_user (time spent waiting on a permission decision, with decision: accept|reject) and claude_code.tool.execution (the actual run, with success/error)2. Only three span types ever carry an ERROR status — llm_request, tool.execution, and hook — everything else always ends UNSET even on failure, because failure for those spans is expressed through their own child spans instead2.

claude_code.hook is a special case: it needs CLAUDE_CODE_ENHANCED_TELEMETRY_BETA=1 plus two additional variables, ENABLE_BETA_TRACING_DETAILED=1 and BETA_TRACING_ENDPOINT. In interactive CLI sessions it also requires your organization to be allowlisted for the feature — but Agent SDK and non-interactive -p sessions are explicitly not gated by that allowlist requirement2, so hook spans are available to SDK consumers today even where they aren't yet to everyone using the CLI directly.

Do these spans follow the OpenTelemetry GenAI semantic conventions?

Partially, and it's worth being precise about which parts. The span namesclaude_code.interaction, claude_code.llm_request, claude_code.tool — are Anthropic's own namespace, not the GenAI conventions' own naming scheme; the conventions themselves define operation-based span names instead, such as invoke_agent for an agent run, chat for an LLM call, and execute_tool for a tool invocation6. Those conventions remain in experimental/development status as of mid-2026 and have moved into their own dedicated semantic-conventions-genai repository, separate from the main OpenTelemetry spec6. But a specific, named subset of attributes on Claude Agent SDK's claude_code.* spans is explicitly documented as implementing the GenAI convention regardless of the span-name mismatch:

SpanAnthropic-specific attributes (examples)OpenTelemetry GenAI semantic convention attributes
claude_code.llm_requestmodel, duration_ms, ttft_ms, input_tokens, output_tokens, stop_reason, query_sourcegen_ai.system (always "anthropic"), gen_ai.request.model, gen_ai.response.id, gen_ai.response.finish_reasons
claude_code.tool / .executiontool_name, result_tokens, file_path, full_commandgen_ai.tool.call.id

Source: the monitoring reference's attribute tables, which label each of these five attributes "OpenTelemetry GenAI semantic convention" individually2. The practical consequence: a backend that dashboards purely by GenAI-convention span names won't recognize claude_code.llm_request as a GenAI operation out of the box, since the name itself isn't part of that convention — but it will find the five gen_ai.* attributes if it queries by attribute instead of by span name. Don't assume full compliance, and don't assume zero relation either.

One of those five is worth its own caveat: gen_ai.system does not appear anywhere in the current OpenTelemetry GenAI attribute registry — the spec has since renamed it to gen_ai.provider.name for the same "which provider handled this call" purpose6. Anthropic's monitoring documentation still labels gen_ai.system as the GenAI-convention attribute Claude Code emits, which means either the CLI's span still literally carries that key, or Anthropic's own docs haven't been updated to the current attribute name — this post reports what Anthropic's documentation and the SDK actually say today, not what the newest spec revision calls for, so don't assume gen_ai.system is the key name a spec-current GenAI backend or dashboard expects.

If you'd rather instrument a hand-rolled tool-calling loop directly against the current GenAI conventions — invoke_agent/chat/execute_tool spans and gen_ai.provider.name rather than the Agent SDK's claude_code.* export — the OpenTelemetry GenAI semantic conventions tracing tutorial builds exactly that, from the raw Messages API rather than the Agent SDK.

Step 5: See real spans without paying for a backend

You don't need Jaeger or a commercial vendor to inspect what the CLI actually sends — a plain HTTP server that logs the request body works, as long as you use the http/json protocol (readable JSON) instead of http/protobuf:

// receiver.ts
import http from "node:http";

const server = http.createServer((req, res) => {
  if (req.method === "POST" && req.url === "/v1/traces") {
    let body = "";
    req.on("data", (chunk) => (body += chunk));
    req.on("end", () => {
      const parsed = JSON.parse(body);
      for (const rs of parsed.resourceSpans ?? []) {
        for (const ss of rs.scopeSpans ?? []) {
          for (const span of ss.spans ?? []) {
            console.log(`${span.name}`);
          }
        }
      }
      res.writeHead(200, { "Content-Type": "application/json" });
      res.end("{}");
    });
  } else {
    res.writeHead(404);
    res.end();
  }
});

server.listen(4318, () => {
  console.log("Listening on http://localhost:4318/v1/traces");
});

Run it directly with node receiver.ts — Node 22.18.0 and later run TypeScript files with only erasable syntax like this one natively, with no flag or compile step. On Node 22.6.0 through 22.17.x, add the flag explicitly: node --experimental-strip-types receiver.ts. Earlier releases, including all of Node 18.x and 20.x, don't support running .ts files natively at all — use npx tsx receiver.ts or compile with tsc first7. Then run index.ts from Step 3 against it. For production use, swap this for an actual collector or an all-in-one Jaeger container — the official guidance is explicit that this kind of minimal listener is for local inspection, not for running a real pipeline1.

// traced-request.ts
import { trace } from "@opentelemetry/api";
import { query } from "@anthropic-ai/claude-agent-sdk";
import { otelEnv } from "./otel-env.js";

const tracer = trace.getTracer("weather-agent-demo");

export async function handleUserRequest(prompt: string) {
  return tracer.startActiveSpan("handle-user-request", async (span) => {
    try {
      for await (const message of query({
        prompt,
        options: { model: "claude-opus-4-8", env: otelEnv },
      })) {
        if (message.type === "result" && message.subtype === "success") {
          span.setAttribute("total_cost_usd", message.total_cost_usd);
        }
      }
    } finally {
      span.end();
    }
  });
}

Auto-injection is skipped if you set TRACEPARENT explicitly in options.env yourself — useful if you need to pin a specific parent context rather than whatever span happens to be active. Interactive CLI sessions ignore inbound TRACEPARENT entirely to avoid picking up ambient values from CI or containers; only Agent SDK and claude -p runs honor it12. One more scope limit worth knowing: the traceparent header is only sent to the Anthropic API directly by default. If you're routing through a custom ANTHROPIC_BASE_URL proxy, propagation is off unless you also set CLAUDE_CODE_PROPAGATE_TRACEPARENT=1, since some proxies reject unrecognized headers2.

Step 7: Tag traces by service, user, and tenant

The default service.name on every span is claude-code1. If more than one agent in your system exports to the same collector, override it and attach whatever metadata you need to filter by later:

const options = {
  env: {
    ...otelEnv,
    OTEL_SERVICE_NAME: "support-triage-agent",
    OTEL_RESOURCE_ATTRIBUTES:
      `service.version=1.4.0,deployment.environment=production,enduser.id=${encodeURIComponent(userId)},tenant.id=${encodeURIComponent(tenantId)}`,
  },
};

Percent-encode attribute values before interpolating them — OTEL_RESOURCE_ATTRIBUTES reserves commas, spaces, and equals signs as its own delimiters1. With enduser.id/tenant.id attached, the tool_decision, tool_result, mcp_server_connection, and permission_mode_changed log events become a per-user audit trail you can forward to a SIEM, instead of just attributing every action to whichever credential the process runs under1.

Controlling what gets captured

Spans are structural by default — durations, model names, and tool names are always recorded, but token counts are only recorded when the underlying API call actually returns usage data (so a failed or aborted request's span may simply omit them), and the content your agent reads and writes is never captured unless you opt in12:

VariableWhat it adds
OTEL_LOG_USER_PROMPTS=1Prompt text on the claude_code.interaction span's user_prompt attribute
OTEL_LOG_TOOL_DETAILS=1Tool input arguments — file paths, shell commands, search patterns — on claude_code.tool
OTEL_LOG_TOOL_CONTENT=1Full tool input/output bodies as a span event, truncated at 60 KB; requires tracing to already be on
OTEL_LOG_RAW_API_BODIESFull Messages API request/response JSON; 1 truncates inline at 60 KB, file:<dir> writes untruncated bodies to disk

Leave all four unset unless your observability pipeline is specifically approved to store what your agent handles — enabling the raw-bodies variable implies consent to everything the other three would separately reveal1.

Shipping traces to a real backend

Two genuinely different routes get you to a dashboard, and they instrument different things:

Native CLI export (this tutorial)OpenInference instrumentation
What's actually instrumentedThe Claude Code CLI subprocess the SDK spawnsYour own process's SDK calls, patched by a JS/Python instrumentation library
Span names you getclaude_code.*OpenInference's own span shape
Extra packages neededNone — just environment variables@arizeai/openinference-instrumentation-claude-agent-sdk, @langfuse/otel, @opentelemetry/sdk-node8
Where it can goAny OTLP-compatible backend: Honeycomb, Datadog, Grafana, a self-hosted collector, or Langfuse1Whichever exporter you wire the NodeSDK to — Langfuse's own docs demonstrate this route specifically8

The OpenInference route (used by Langfuse's official JS/TS integration) is a separate community instrumentation layer, not an alternative configuration of the same CLI-level export this tutorial builds — the two can coexist, but don't expect them to produce identical span trees8. If your goal is prompt/response review, scoring, and dataset curation, the OpenInference-to-Langfuse route is built for that. If your goal is ops-style dashboards, cost attribution per tenant, or a SIEM-forwardable audit trail, the native CLI export this tutorial covers is the more direct path, since it doesn't require adding an instrumentation dependency to your own process at all.

Cost and latency without any of this

If all you need is one number per run, you don't need OpenTelemetry at all. The result message on the SDK's own response stream already carries total_cost_usd, duration_ms, num_turns, and a full usage/modelUsage breakdown for the whole query() call, with no exporter configuration required4. Tracing is for when that one aggregate number isn't enough to answer which step was expensive — the two are complementary, not competing, and Anthropic's own observability guide links out to the cost-tracking doc for exactly this reason1.

Verification

What was actually run: every code sample above was installed against the real, currently-published @anthropic-ai/claude-agent-sdk@0.3.210 (npm registry, confirmed 2026-07-15) plus zod@4.4.3, @types/node@26.1.1, typescript@7.0.2, and @opentelemetry/api@1.9.1, and type-checked with tsc --strict --target es2022 --module nodenext --moduleResolution nodenext. All five TypeScript files — otel-env.ts, weather-tool.ts, index.ts, traced-request.ts, and receiver.ts — compiled together with zero errors. The receiver.ts server was actually started (node, listening on port 4318) and sent a hand-constructed OTLP/JSON payload via curl containing the real span names from Step 4; the receiver correctly parsed it and printed all three span names, confirming the parsing logic works against the documented payload shape.

What was not run: a live query() call against the real Claude API. That requires a paid ANTHROPIC_API_KEY, which this automated writing pipeline doesn't have access to, so no genuine claude_code.* spans were captured from an actual agent run — the span tree and attribute names in this post are transcribed directly from Anthropic's own documented reference, not from an independent capture2. If you have a key, run:

export ANTHROPIC_API_KEY=sk-ant-...
npx tsx index.ts

You should see the weather answer printed, followed by total_cost_usd and duration_ms from the result message, and — if receiver.ts is running and OTEL_EXPORTER_OTLP_ENDPOINT points at it — a claude_code.interaction line followed by claude_code.llm_request and claude_code.tool lines in the receiver's own console.

Troubleshooting

No spans arrive anywhere. Confirm both CLAUDE_CODE_ENABLE_TELEMETRY=1 and CLAUDE_CODE_ENHANCED_TELEMETRY_BETA=1 are set — traces need both, unlike metrics and logs, which only need the first12.

Spans arrive but every attribute you expected is missing. Content capture is opt-in. If user_prompt, tool inputs, or tool bodies are missing, that's the default redaction behavior, not a bug — set the relevant OTEL_LOG_* variable from the table above1.

Nothing shows up in stdout, or the process seems to hang oddly. Check you haven't set console as an exporter value — the SDK already uses stdout as its message channel, and the two will collide1.

Traces don't nest under my application's span. Confirm your own span is genuinely active (via startActiveSpan, not just created) at the moment query() is called, and that you haven't set TRACEPARENT explicitly in options.env, which skips auto-injection entirely1.

Everything above works locally but nothing arrives when routed through a custom base URL. If ANTHROPIC_BASE_URL points at anything other than the Anthropic API directly, set CLAUDE_CODE_PROPAGATE_TRACEPARENT=1 — propagation to non-Anthropic endpoints is off by default2.

Next steps and further reading

This tutorial traces built-in and SDK-defined tools; the same claude_code.tool spans apply to tools exposed over MCP, which is worth combining with the production MCP server tutorial with OAuth and streamable HTTP if your agent's tools live behind an authenticated MCP endpoint. If you're also adding an approval layer in front of tool calls, the human-in-the-loop Claude Agent SDK tutorial covers the canUseTool callback and PreToolUse/PostToolUse hooks for building your own hand-rolled audit log — a different mechanism from the tool_decision log event this post's audit-trail section covers, since that one is emitted automatically once OTEL_LOGS_EXPORTER is on rather than written by a hook you author yourself, but the two are complementary places to look for the same kind of "what did the agent actually do" record. And if your agent needs to remember things across sessions rather than just be observable within one, the Claude memory tool and context editing tutorial covers the SDK's persistent-memory feature, which is a different package concern entirely from the tracing setup here.

Footnotes

  1. Anthropic, "Observability with OpenTelemetry" — https://code.claude.com/docs/en/agent-sdk/observability (three-signal model, CLAUDE_CODE_ENABLE_TELEMETRY/CLAUDE_CODE_ENHANCED_TELEMETRY_BETA flags, TypeScript env replace-vs-merge behavior, W3C trace-context propagation, OTEL_SERVICE_NAME/OTEL_RESOURCE_ATTRIBUTES, content-capture opt-in variables; fetched 2026-07-15) 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23

  2. Anthropic, "Monitoring" — https://code.claude.com/docs/en/monitoring-usage, "Traces (beta)" section (span hierarchy diagram, per-span attribute tables, gen_ai.* attributes individually labeled "OpenTelemetry GenAI semantic convention," claude_code.hook allowlist scope, CLAUDE_CODE_PROPAGATE_TRACEPARENT, OTel status semantics; fetched 2026-07-15) 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19

  3. npm registry, @anthropic-ai/claude-agent-sdkhttps://registry.npmjs.org/@anthropic-ai/claude-agent-sdk (version 0.3.210, published 2026-07-14T19:39:19.351Z per this version's own registry time entry, engines.node >=18.0.0, bundled claudeCodeVersion 2.1.210, peerDependencies zod ^4.0.0/@anthropic-ai/sdk >=0.93.0/@modelcontextprotocol/sdk ^1.29.0; fetched directly from the registry API, 2026-07-15) 2 3 4

  4. @anthropic-ai/claude-agent-sdk@0.3.210 compiled type definitions, sdk.d.ts — installed from npm and inspected directly, 2026-07-15 (Options.env replace-semantics comment, query()/tool()/createSdkMcpServer() signatures, SDKResultSuccess fields including total_cost_usd/duration_ms/usage/modelUsage) 2 3 4

  5. Anthropic, "Models overview" — https://platform.claude.com/docs/en/about-claude/models/overview (current model IDs and pricing: Claude Opus 4.8 claude-opus-4-8 at $5/$25 per MTok; fetched 2026-07-15)

  6. OpenTelemetry, "Generative AI semantic conventions" — https://opentelemetry.io/docs/specs/semconv/gen-ai/ (page explicitly states GenAI semantic conventions have moved to a dedicated semantic-conventions-genai repository and are no longer maintained in the main spec repo; fetched 2026-07-15). Development/experimental status as of this writing confirmed separately via OpenTelemetry, "GenAI observability" blog post, May 14, 2026 — https://opentelemetry.io/blog/2026/genai-observability/ ("under active development"), and via the invoke_agent/chat/execute_tool span names that blog post documents as the GenAI conventions' own naming scheme — distinct from claude_code.*. That same registry page's full attribute table (fetched directly, 2026-07-15) lists gen_ai.provider.name but contains no entry for a bare gen_ai.system anywhere, alphabetically or otherwise — only the distinct gen_ai.system_instructions attribute appears — consistent with gen_ai.system having been renamed to gen_ai.provider.name in the current spec. 2 3 4

  7. Node.js, "Running TypeScript Natively" — https://nodejs.org/learn/typescript/run-natively (v22.18.0+ runs erasable-syntax .ts files with no flag; earlier versions need --experimental-strip-types; type stripping does not type-check, enum/namespaces/parameter properties aren't supported; fetched 2026-07-15)

  8. Langfuse, "Observability for Claude Agent SDK JS/TS with Langfuse" — https://langfuse.com/integrations/frameworks/claude-agent-sdk-js (install command listing @anthropic-ai/claude-agent-sdk, @arizeai/openinference-instrumentation-claude-agent-sdk, @langfuse/otel, and @opentelemetry/sdk-node together; NodeSDK/LangfuseSpanProcessor setup; the page's own runnable example is a tool-free prompt on claude-sonnet-4-5, not a weather tool; fetched 2026-07-15) 2 3 4

  9. Langfuse, "Pricing" — https://langfuse.com/pricing ("Hobby plan is completely free and does not require a credit card," 100k units/month included) and Langfuse, "Self-Hosted Pricing" — https://langfuse.com/pricing-self-host (Community tier listed as "Free," MIT License, "All core platform features and APIs"; both fetched directly, 2026-07-15)

Frequently Asked Questions

No. It's explicitly documented as beta — span names and attributes may change between SDK releases, which is also why it needs its own CLAUDE_CODE_ENHANCED_TELEMETRY_BETA flag separate from metrics and logs12.