gemini-cli - 💡(How to fix) Fix OpenTelemetry: TRACEPARENT env var is ignored, breaking distributed tracing across process boundaries [1 participants]

Official PRs (…)
ON THIS PAGE

Recommended Tools

×6

Utilities matched from this issue’s tags and category — try them while you read without losing context.

GitHub issue graph ai analysis

Paste a GitHub issue URL. We fetch that issue, discover linked issues from bodies/comments/timeline, collect linked pull requests, and produce a structured English report.

The report is written in English Markdown for sharing and archival.

Helpful · Quick feedback

Loading…
GitHub stats
google-gemini/gemini-cli#25919Fetched 2026-04-25 06:22:24
View on GitHub
Comments
0
Participants
1
Timeline
1
Reactions
1
Author
Participants
Timeline (top)
labeled ×1

When a parent process spawns gemini as a subprocess and injects a W3C TRACEPARENT (and TRACESTATE) into its environment — the standard way to propagate OTel context across process boundaries — the Gemini CLI ignores it and starts a brand-new root trace. This breaks distributed tracing for any system that embeds the CLI as a subprocess.

Root Cause

I'm building an agent harness that spawns gemini in ACP mode per user session. The orchestrator emits its own OTel spans (job lifecycle, orchestration, Slack I/O). Without cross-process parenting, operators have to filter traces manually by a shared attribute (we use thread.ts) and open two separate traces in the Phoenix UI to debug one user interaction. A one-line extract at startup would give us a single coherent trace.

Code Example

import os, subprocess
from opentelemetry import trace
from opentelemetry.propagate import inject
from opentelemetry.sdk.trace import TracerProvider

trace.set_tracer_provider(TracerProvider())
tracer = trace.get_tracer("repro")

with tracer.start_as_current_span("parent_job") as span:
    env = os.environ.copy()
    inject(env)  # writes "traceparent" key
    env["TRACEPARENT"] = env.pop("traceparent")  # uppercase for env-var convention
    subprocess.run(["gemini", "-p", "hello"], env=env)

---

import { context, propagation, trace } from "@opentelemetry/api";

const traceparent = process.env.TRACEPARENT;
if (traceparent) {
  const carrier = {
    traceparent,
    tracestate: process.env.TRACESTATE,
  };
  const extractedCtx = propagation.extract(context.active(), carrier);
  // Use extractedCtx as the parent when starting the root span:
  await context.with(extractedCtx, async () => {
    // ...existing root span creation (e.g. the UserPrompt root from #23057)...
  });
}
RAW_BUFFERClick to expand / collapse

What happened?

Summary

When a parent process spawns gemini as a subprocess and injects a W3C TRACEPARENT (and TRACESTATE) into its environment — the standard way to propagate OTel context across process boundaries — the Gemini CLI ignores it and starts a brand-new root trace. This breaks distributed tracing for any system that embeds the CLI as a subprocess.

Related work

This is related to but distinct from #23057, which proposed wrapping non-interactive execution in a UserPrompt root span to unify the CLI's own internal spans (intra-process correlation). That PR was auto-closed unmerged on 2026-04-03.

This issue is one level up: even with a local root span in place, that root has no upstream parent because nothing reads TRACEPARENT from the environment. The two fixes are complementary — local correlation + cross-process propagation together would give a fully connected trace.

Why this matters

I'm building an agent harness that spawns gemini in ACP mode per user session. The orchestrator emits its own OTel spans (job lifecycle, orchestration, Slack I/O). Without cross-process parenting, operators have to filter traces manually by a shared attribute (we use thread.ts) and open two separate traces in the Phoenix UI to debug one user interaction. A one-line extract at startup would give us a single coherent trace.

What did you expect to happen?

Expected behavior

On startup, if process.env.TRACEPARENT is set, the CLI should parse it and use the resulting SpanContext as the parent of its root span (e.g. agent_call, or the UserPrompt root proposed in #23057). This is the convention used by OTel SDKs in other languages (Python's opentelemetry.propagate.inject writes TRACEPARENT into env; the child is expected to pick it up).

Actual behavior

Every gemini invocation generates a fresh random trace ID regardless of TRACEPARENT. The CLI's internal span tree (agent_call → llm_call → schedule_tool_calls → tool_call) is correctly nested in-process, but it's detached from any upstream trace.

Reproduction

Parent process (Python):

import os, subprocess
from opentelemetry import trace
from opentelemetry.propagate import inject
from opentelemetry.sdk.trace import TracerProvider

trace.set_tracer_provider(TracerProvider())
tracer = trace.get_tracer("repro")

with tracer.start_as_current_span("parent_job") as span:
    env = os.environ.copy()
    inject(env)  # writes "traceparent" key
    env["TRACEPARENT"] = env.pop("traceparent")  # uppercase for env-var convention
    subprocess.run(["gemini", "-p", "hello"], env=env)

With a Phoenix/OTel collector receiving spans from both processes, you'll observe two separate traces with different trace IDs — the parent's parent_job and the CLI's agent_call — instead of one nested trace.

Evidence from the bundle

I grepped the installed @google/gemini-cli bundle (installed globally via npm). The W3CTraceContextPropagator is present as part of @opentelemetry/core, but no application code reads process.env.TRACEPARENT or calls propagation.extract against it during telemetry initialization. The propagator is only wired for cross-request HTTP propagation — not process-boundary propagation.

Client information

Environment

@google/gemini-cli installed via npm install -g Node v24.14.1 / v18.20.8 (reproduced on both) OTel collector: otelcol-contrib, OTLP/gRPC receiver Backend: Arize Phoenix

Gemini

│ About Gemini CLI │ │ │ │ CLI Version 0.37.0 │ │ Git Commit 352bbe149 │ │ Model Auto (Gemini 3) │ │ Sandbox no sandbox │ │ OS linux │ │ Auth Method vertex-ai │ │ IDE Client VS Code

Login information

Vertex AI

Anything else we need to know?

Suggested fix

In the telemetry bootstrap, before creating the root span:

import { context, propagation, trace } from "@opentelemetry/api";

const traceparent = process.env.TRACEPARENT;
if (traceparent) {
  const carrier = {
    traceparent,
    tracestate: process.env.TRACESTATE,
  };
  const extractedCtx = propagation.extract(context.active(), carrier);
  // Use extractedCtx as the parent when starting the root span:
  await context.with(extractedCtx, async () => {
    // ...existing root span creation (e.g. the UserPrompt root from #23057)...
  });
}

Both TRACEPARENT (uppercase env-var convention) and traceparent (lowercase header convention) should be accepted for robustness.

extent analysis

TL;DR

To fix the issue, modify the Gemini CLI's telemetry bootstrap to extract the parent span context from the TRACEPARENT environment variable and use it as the parent when creating the root span.

Guidance

  • Verify that the TRACEPARENT environment variable is being set correctly in the parent process by checking its value before spawning the Gemini subprocess.
  • In the Gemini CLI's telemetry bootstrap, extract the parent span context from the TRACEPARENT environment variable using the propagation.extract method.
  • Use the extracted context as the parent when creating the root span, as shown in the suggested fix code snippet.
  • Ensure that both TRACEPARENT (uppercase env-var convention) and traceparent (lowercase header convention) are accepted for robustness.

Example

The suggested fix code snippet provided in the issue body demonstrates how to extract the parent span context and use it as the parent when creating the root span:

const traceparent = process.env.TRACEPARENT;
if (traceparent) {
  const carrier = {
    traceparent,
    tracestate: process.env.TRACESTATE,
  };
  const extractedCtx = propagation.extract(context.active(), carrier);
  await context.with(extractedCtx, async () => {
    // ...existing root span creation...
  });
}

Notes

This fix assumes that the @opentelemetry/api package is being used in the Gemini CLI's telemetry bootstrap. If a different package or version is being used, the fix may need to be modified accordingly.

Recommendation

Apply the suggested fix to the Gemini CLI's telemetry bootstrap to enable cross-process parenting and distributed tracing. This will allow the Gemini CLI to correctly propagate the parent span context and create a connected trace.

Vote matrix · Quick signals

Works
Did the solution work? Tap to confirm.
Easy Fix
Was it a quick fix?
Time Saver
Did it save you time?
Blocking
Was it severely blocking?
Common Issue
Are others likely hitting this too?
Flaky / Intermittent
Is it intermittent?
Verified / Reproducible
Can you reproduce it reliably?
Loading…

FAQ

Expected behavior

On startup, if process.env.TRACEPARENT is set, the CLI should parse it and use the resulting SpanContext as the parent of its root span (e.g. agent_call, or the UserPrompt root proposed in #23057). This is the convention used by OTel SDKs in other languages (Python's opentelemetry.propagate.inject writes TRACEPARENT into env; the child is expected to pick it up).

Still need to ship something?

×6

Another batch ranked right after the header list — different links, same matching logic.

Back to top recommendations

TRENDING

gemini-cli - 💡(How to fix) Fix OpenTelemetry: TRACEPARENT env var is ignored, breaking distributed tracing across process boundaries [1 participants]