[BUG] claude subprocess does not inherit active OTel span as parent — requires manual OTEL_TRACEPARENT injection
Summary
When the claude CLI is invoked as a subprocess from within an active OTel span, it does not automatically inherit that span as its parent, and its spans do not appear in the calling process's trace. Callers must manually inject both OTEL_TRACEPARENT (for parent context) and OTel exporter configuration (endpoint + auth headers) into the subprocess environment — none of which is documented.
This is inconsistent with how every other instrumented outbound call works: HTTP clients, gRPC stubs, etc. propagate context automatically. The claude subprocess is an outbound call that the SDK should instrument transparently.
Environment
- Claude Code CLI v2.1.112 / headless
claudesubprocess (invoked programmatically) - OTel backend: Honeycomb (OTLP HTTP); reproducible with any backend
- OS: Linux
Reproduction
Script run on 2026-04-27. Full repro at bottom of this issue.
Orchestrator opens a span, then calls claude twice — pass 1 with OTEL_TRACEPARENT set, pass 2 without:
[repro] trace_id = 4f31c28b55b70c30ae966a94f9a81944
[repro] Pass 1: calling claude WITH OTEL_TRACEPARENT set
[repro] Pass 1 exit code: 0
[repro] Pass 2: calling claude WITHOUT OTEL_TRACEPARENT
[repro] Pass 2 exit code: 0
Result in Honeycomb (trace 4f31c28b55b70c30ae966a94f9a81944, local environment):
- 1 span total — only
orchestrator.run - 0 children, 0 descendants
- Neither pass 1 nor pass 2 subprocess spans appear anywhere in the trace
The subprocess spans are completely missing from the orchestrator trace — not orphaned within it, but absent entirely. They either land in a disconnected root trace or are dropped, depending on whether the subprocess has its own exporter configured.
Root Cause
The claude subprocess has its own OTel initialization that is independent of the calling process's OTel setup. It does not:
- Inherit the caller's active span as its parent (no automatic
OTEL_TRACEPARENTinjection) - Export to the caller's OTel endpoint (needs its own exporter config)
For subprocess spans to appear in the orchestrator's trace, callers must manually inject all of the following into the subprocess environment before each invocation:
OTEL_TRACEPARENT— W3C traceparent of the currently active orchestrator spanOTEL_EXPORTER_OTLP_ENDPOINT— the OTLP endpoint to export toOTEL_EXPORTER_OTLP_HEADERS— auth headers (e.g.x-honeycomb-team=<key>)
None of this is documented. There is no SDK-level warning when these are absent.
Expected Behavior
The SDK should snapshot the active OTel span context at subprocess launch time and inject it as OTEL_TRACEPARENT into the subprocess environment automatically — the same way an HTTP client injects traceparent into outbound request headers. No manual injection should be required.
Exporter configuration is a harder problem (the subprocess may need different auth), but at minimum the parent context propagation should be automatic.
Workaround
Manually inject the active span context and exporter config before each invocation:
from opentelemetry import propagate
carrier = {}
propagate.inject(carrier) # reads the currently active span
subprocess_env = {
**base_env,
**carrier, # OTEL_TRACEPARENT, OTEL_TRACESTATE
"OTEL_EXPORTER_OTLP_ENDPOINT": "https://...",
"OTEL_EXPORTER_OTLP_HEADERS": "x-honeycomb-team=...",
}
result = run_claude("do some work", env=subprocess_env)
Impact
- Severity: Medium (telemetry correctness, no runtime impact)
- Who is affected: Any orchestrator that invokes
claudeas a subprocess from within a traced context. Subprocess spans are completely absent from the calling trace without the workaround.
Repro Script
#!/usr/bin/env python3
"""
pip install opentelemetry-sdk opentelemetry-exporter-otlp-proto-http
"""
import os, subprocess, sys, time
from opentelemetry import trace
from opentelemetry.sdk.trace import TracerProvider
from opentelemetry.sdk.trace.export import BatchSpanProcessor
from opentelemetry.exporter.otlp.proto.http.trace_exporter import OTLPSpanExporter
from opentelemetry.sdk.resources import Resource
from opentelemetry.trace.propagation.tracecontext import TraceContextTextMapPropagator
OTLP_ENDPOINT = os.environ.get("OTEL_EXPORTER_OTLP_ENDPOINT", "http://localhost:4318")
provider = TracerProvider(resource=Resource.create({"service.name": "orphan-repro-test"}))
provider.add_span_processor(BatchSpanProcessor(OTLPSpanExporter(endpoint=f"{OTLP_ENDPOINT}/v1/traces")))
trace.set_tracer_provider(provider)
tracer = trace.get_tracer("orphan-repro")
def traceparent(span):
ctx = span.get_span_context()
return f"00-{ctx.trace_id:032x}-{ctx.span_id:016x}-{'01' if ctx.trace_flags else '00'}"
with tracer.start_as_current_span("orchestrator.run") as root:
tid = f"{root.get_span_context().trace_id:032x}"
print(f"trace_id: {tid}")
# Pass 1: OTEL_TRACEPARENT injected
env1 = {**os.environ, "OTEL_TRACEPARENT": traceparent(root),
"OTEL_EXPORTER_OTLP_ENDPOINT": OTLP_ENDPOINT}
subprocess.run(["claude", "-p", "say hello", "--output-format", "text"], env=env1)
time.sleep(2)
# Pass 2: no OTEL_TRACEPARENT (bug trigger)
env2 = {**os.environ, "OTEL_EXPORTER_OTLP_ENDPOINT": OTLP_ENDPOINT}
subprocess.run(["claude", "-p", "say goodbye", "--output-format", "text"], env=env2)
provider.force_flush(timeout_millis=15000)
print(f"\nCheck trace {tid} in your OTel backend — expect 0 children under orchestrator.run")
Related
- #48862 — missing docs for inbound
TRACEPARENT/TRACESTATEenv var support - #50776 — non-standard token attribute names on
claude_code.llm_requestspans
This issue has 4 comments on GitHub. Read the full discussion on GitHub ↗