anthropics / anthropics/claude-agent-sdk-python

Per-message trace-context propagation for persistent sessions — TRACEPARENT is injected only at connect(), so all MCP/tool spans attribute to the first turn's trace

未關閉
#1,126 0 則留言 0 個 reaction 已指派 0 人 在 GitHub 檢視
documentation enhancement
主要語言
Python
星號
8.1k
分支
1.3k
平均合併
2 天 31 分鐘
30 天內合併 PR
1

描述

## Feature request

A per-message mechanism to update the CLI subprocess's W3C trace context for long-lived `ClaudeSDKClient` sessions — today `TRACEPARENT`/`TRACESTATE` are injected into the child process environment **once, at `connect()`**, and never again.

## Current behavior

`_internal/transport/subprocess_cli.py` (v0.2.121, ~L504–528) reads the ambient OpenTelemetry span context at transport spawn and injects `TRACEPARENT`/`TRACESTATE` into the CLI child-process env. The CLI honors it (with `CLAUDE_CODE_ENABLE_TELEMETRY=1` + `CLAUDE_CODE_ENHANCED_TELEMETRY_BETA=1`, plus `CLAUDE_CODE_PROPAGATE_TRACEPARENT=1` when a custom base URL is in use) and stamps outbound MCP HTTP tool calls with `traceparent` — with **fresh span IDs per tool call**, but always the **spawn-time trace ID**, for the lifetime of the process.

For a persistent session serving many user turns, the host application typically starts a new trace per turn (the turn-per-trace model used by LLM observability backends such as Langfuse). The host-side spans land in each turn's trace correctly, but every MCP/tool span from every turn attributes to the **first turn's** trace. Effects:

- The first turn's trace accumulates the whole session's tool activity; its trace-window duration reads minutes for a seconds-long turn.
- Turns ≥2 appear to make no tool calls at all in their own traces.

We audited the control-protocol message subtypes in v0.2.121 and found no field that carries trace context on the streaming input envelope, so there is no host-side workaround: `options.env` is spawn-time-only, and the context cannot cross the SDK boundary afterward.

## Reproduction sketch

1. Streamable-HTTP MCP server (e.g. FastMCP) with one trivial tool, wrapped in ASGI middleware that logs the inbound `traceparent` header per request.
2. Host: OTel `TracerProvider` + any instrumentation that opens a span around each `query()` (or open one manually).
3. `ClaudeSDKClient` with telemetry env: `CLAUDE_CODE_ENABLE_TELEMETRY=1`, `CLAUDE_CODE_ENHANCED_TELEMETRY_BETA=1`, `CLAUDE_CODE_PROPAGATE_TRACEPARENT=1`, `OTEL_TRACES_EXPORTER=otlp`, `OTEL_EXPORTER_OTLP_PROTOCOL=http/protobuf`, `OTEL_EXPORTER_OTLP_ENDPOINT=`.
4. Send two prompts on the same client, each inside a **different** host span (different trace IDs), each forcing a tool call.
5. Observe: both tool calls' `traceparent` headers carry the trace ID that was ambient at `connect()`; the second turn's new trace ID never reaches the wire.

## Ask

Any of these would solve it (in our order of preference):

1. **Re-read the ambient OTel context per `query()`** and forward it to the CLI (control protocol or input envelope), so the CLI parents its per-interaction spans — and stamps outbound MCP calls — under the caller's current trace.
2. **Accept trace context on the streaming input message** (an explicit field the host sets per message).
3. Expose a documented control-protocol request to update `TRACEPARENT` mid-session.

Related work, for context: anthropics/claude-code#31039 (the spawn-time injection feature as shipped), anthropics/claude-code#76391 (SEP-414 `_meta` trace-context propagation for MCP — complementary: it addresses the MCP hop, but the CLI-side parent context would still be spawn-frozen), and #952 in this repo (adjacent).

## Side observation (worth a docs note even if the feature is declined)

`OTEL_EXPORTER_OTLP_PROTOCOL=http/protobuf` is load-bearing for the CLI's tracing: with an HTTP OTLP endpoint configured but the protocol var unset, the exporter apparently defaults to gRPC, fails to construct, and the entire telemetry subsystem — span creation included, hence also traceparent stamping on MCP calls — dies silently. A one-line note in the observability docs ("set the protocol explicitly") would save others the bisection.

## Environment

- `claude-agent-sdk==0.2.121` (Python), macOS + Linux (containerized)
- Bedrock mode (`CLAUDE_CODE_USE_BEDROCK=1`, custom `ANTHROPIC_BEDROCK_BASE_URL`)
- Streamable-HTTP MCP server (FastMCP)
- Verified against 0.1.48 as well: that CLI generation does not stamp `traceparent` on MCP calls at all, even with telemetry fully active — the behavior described above is the current (0.2.x) one.

## Our mitigation meanwhile

Server-side: MCP tool spans are detached into their own traces and joined to the calling turn via the `claudecode/toolUseId` the CLI already sends in `_meta` (thank you for that — it's the only per-call key that crosses the boundary today, and it works). The feature above would let those spans nest under the correct turn natively.

貢獻指南

這個儲存庫沒有索引到貢獻指南

研究方向

Start by reading _internal/transport/subprocess_cli.py around the spawn-time TRACEPARENT/TRACESTATE injection noted in the issue, then reproduce with a persistent ClaudeSDKClient and a FastMCP server logging inbound traceparent headers. The issue is done only when a maintainer-approved per-message trace-context path exists and two queries in different host traces produce MCP/tool spans under their respective trace IDs.

由索引模型根據 Issue 內容生成。

評估

技術堆疊
python
領域
cli, observability
Issue 類型
功能
難度
5/5
預估耗時
一週以上
活躍度
冷清
描述清晰度
基本清楚
新手友好度
28/100

把新 issue 寄到你的電子郵件信箱

精選適合新手參與的 GitHub issue 摘要。