Help us stabilize environment variable context propagation
A trace does not always cross a network boundary. A workflow runner starts a shell, the shell launches a build tool, and the build tool starts test processes. Batch and data-processing systems create similar chains of child processes. Without a shared way to pass trace information across these boundaries, spans from each process can end up in separate traces.
If context propagation is new to you, it is the mechanism that carries information from one service or process to the next. For tracing, this includes the trace and span identifiers that let new spans join the same trace. It can also carry baggage: application-defined key-value pairs that are passed to downstream work.
The OpenTelemetry Specification now has a release candidate for using environment variables as context propagation carriers. It standardizes how propagators can use environment variables to carry data such as trace context and baggage between processes when protocol headers or message metadata are not available.
Before we mark this specification Stable, we want feedback from language implementers, tool authors, platform engineers, and users operating real CI/CD, batch, and command-line workloads.
What is an environment variable carrier?
A carrier is the medium used to pass propagation data between services or processes. A propagator writes propagation fields to the carrier and reads them on the receiving side. HTTP headers are a familiar carrier. When one process starts another, the child process’s environment can play the same role: propagation fields are written before the child starts and read during its initialization.
The release candidate defines rules to make this work consistently across
platforms. In the specification, the propagator type used with string key-value
carriers is called a TextMapPropagator.
For example, when using the W3C Trace Context and W3C Baggage propagators, the propagation fields are represented by environment variables such as:
TRACEPARENT=00-4bf92f3577b34da6a3ce929d0e0e4736-00f067aa0ba902b7-01
TRACESTATE=vendorname=opaquevalue
BAGGAGE=build.id=42,repository.name=example
The OpenTelemetry Propagators API calls writing propagation fields to a carrier injection and reading them extraction.
Inside a process, OpenTelemetry holds propagation data in a Context. For
tracing, that data includes SpanContext; it can also include Baggage. During
injection, a propagator reads data from the current Context and writes it to
the carrier. During extraction, it reads the carrier and returns a new Context
containing that data.
The environment carrier does not interpret these values; it treats them as plain strings. The configured propagator remains responsible for choosing field names and validating and parsing values. This keeps the carrier usable with W3C formats as well as formats such as B3.
Environment variable names have more restrictions than HTTP header names, so the specification defines a normalization algorithm. It converts ASCII letters to uppercase, replaces unsupported characters with underscores, and ensures that a name does not start with a digit. For example:
x-b3-traceid -> X_B3_TRACEID
The rules apply consistently when an implementation reads (Get), writes
(Set), or lists (Keys) environment variables, including on case-insensitive
platforms such as Windows.
How propagation works between processes
The intended lifecycle follows the way process environments already work:
- During initialization, configured propagators extract values from the
environment the child process received at startup into a
Context. - The application creates spans and performs work using that
Context. - Before starting another child, the application copies the environment and
injects propagation fields from its current
Contextinto that copy. - The application starts the child with the modified environment, and the cycle repeats.
flowchart TD
A["Extract context<br/>from environment"] --> B["Create spans<br/>and do work"]
B --> C["Copy environment<br/>and inject context"]
C --> D["Start child with<br/>copied environment"]
D -.->|repeat in child| AApplications should use a separate environment copy for each child. This is especially important when several processes run concurrently and belong to different spans. Treating propagation variables as startup input also avoids relying on mutations to the parent process’s global environment.
Use cases and implementations
OpenTelemetry language implementations
OpenTelemetry language implementations can provide helpers that let a configured propagator read from and write to a process environment. Depending on the language, a helper might be a carrier type, a getter and setter, or another language-appropriate API. It may live in an API, SDK, or contrib package. Instrumentation can use these helpers to extract incoming values at process startup and inject fields into a copied environment before starting a child.
These helpers do not start child processes. Application code or instrumentation starts the child and passes the prepared environment to the relevant process API. The configured propagator handles its propagation format, so integrations do not need to parse W3C Trace Context, W3C Baggage, B3, or other supported formats themselves.
Command-line tools such as otel-cli
A command-line tool can apply the same pattern without requiring every shell
script to integrate with an OpenTelemetry API directly. With tracing configured,
for example by setting an OTLP endpoint, otel-cli can create a span around a
command and inject that span’s TRACEPARENT into the command’s environment:
otel-cli exec --service build --name compile -- make all
By default, when a valid incoming TRACEPARENT is present, otel-cli uses it
as the parent of the span it creates. If make, a process that it launches, or
another otel-cli invocation extracts the propagated TRACEPARENT, its spans
can remain in the same trace. This is a practical example of using environment
variables to continue a trace across command boundaries. OpenTelemetry language
implementations provide the same underlying building blocks for instrumented
applications and libraries.
Shell scripts
Thoth provides OpenTelemetry instrumentation for shell scripts as well as
GitHub Actions. For shell scripts, sourcing its otel.sh file initializes the
project’s SDK and automatic instrumentation. It creates a root span for the
script, creates spans for supported commands, and continues instrumentation into
child shells and executable scripts that use a shebang. It can also inject W3C
traceparent headers through supported HTTP clients such as curl and wget.
Thoth propagates TRACEPARENT and TRACESTATE through child-process
environments.
GitHub Actions
Environment propagation for GitHub Actions is not only hypothetical. Two independent community projects illustrate different approaches:
- Thoth also provides workflow-level and job-level instrumentation. Its
job-level instrumentation runs on the GitHub runner, injects instrumentation
into shell, Node.js, Docker, and composite action steps, and uses
TRACEPARENTandTRACESTATEto continue context into child processes. - Run with Telemetry wraps a particular command in a span and places the
resulting
TRACEPARENTin that command’s environment. Its optionaljob-as-parentmode lets the generated command span join a trace representing the wider job.
Jenkins
The Jenkins OpenTelemetry plugin provides a concrete example in an
established CI system. It exposes the current TRACEPARENT and TRACESTATE in
the environment of shell, batch, and PowerShell steps. An OpenTelemetry-aware
build or test tool invoked by a step can use that context to connect its spans
to the Jenkins pipeline trace.
The plugin also has a configuration option for exporting selected OTEL_* SDK
configuration variables to downstream tools. These variables solve a different
problem: TRACEPARENT and TRACESTATE carry trace context, while variables
such as OTEL_EXPORTER_OTLP_ENDPOINT configure how the downstream process
handles telemetry.
Argo Workflows
Argo Workflows is another useful example. Argo models workflow steps as
containers running on Kubernetes. A workflow integration could inject the
propagation fields into each container’s environment. Instrumented code or a
tool such as otel-cli could then extract those fields and continue the trace.
flowchart TD
C["Workflow integration"] -->|injects fields| A["Container in Pod A<br/>own environment"]
C -->|injects fields| B["Container in Pod B<br/>own environment"]Each target container needs a separate injection because Kubernetes Pods do not inherit one another’s process environments. Inside a container, the same carrier can pass propagation fields to any child processes it starts.
The mechanism also applies to batch schedulers, ETL systems, test runners, and other environments where work is connected through process creation rather than a request protocol.
Security and limitations
Environment variables are accessible to all code running in a process. On some systems they may also be visible to other processes or users with sufficient permissions. Do not use propagation variables for secrets, and review baggage before passing it across a trust boundary. Receiving processes must treat the propagation fields as untrusted input and let the configured propagator validate them.
An environment carrier only transports propagation fields. It does not create spans, configure an SDK, replace propagation through network protocols, or propagate automatically between containers or Kubernetes Pods.
Please report problems now
The document is currently marked Release Candidate. Implementations are available in several OpenTelemetry languages, and current coverage is recorded in the specification compliance matrix. Broader implementation work is tracked in SDK implementation tracker issue 4771.
Feedback period: We will wait until at least November 2, 2026, and until at least 14 days have passed without a new related issue being reported before stabilizing the specification. If significant findings require specification updates, we will restart the 14-day feedback period when those updates are available for review. This gives the community time to validate any substantial changes before stabilization.
We now want to determine whether the requirements are sufficiently clear, portable, secure, and implementable to mark the document Stable. In particular, we would value feedback on these questions:
- Do the normalization rules work for your operating systems and runtimes?
- Can your language implementation expose extraction and injection in a language-appropriate way?
- Is the process-startup and child-environment guidance clear enough?
- Does the model work for CI/CD systems such as GitHub Actions and Argo Workflows, as well as batch and command-line tooling?
- Are any concurrency, security, or trust-boundary concerns missing?
- Does any requirement lead different implementations to incompatible behavior?
Please read the environment variable carrier specification and evaluate it against an implementation or a concrete use case. When you find a problem, report it where it can be acted on:
- For unclear or incorrect requirements, portability problems, missing use cases, or specification-level security concerns, open an issue in the OpenTelemetry Specification repository. Add a comment to stabilization issue #5040 linking the new issue.
- For behavior specific to one language implementation, open an issue in that implementation’s repository and cross-reference the implementation tracker.
- For behavior specific to a tool or workflow platform, open an issue in that project’s repository. If it also reveals a gap in the specification, create a specification issue and connect the two.
A useful report includes the process or workflow being instrumented, operating system and runtime, configured propagator, environment variable names, expected and actual behavior, and a minimal example when possible. Please also call out whether the problem involves concurrent children, name normalization, or a security boundary.
React to the stabilization issue with a thumbs-up to help prioritize the work. Add a comment when you find a blocker or can share concrete implementation or production experience; comments containing only “+1” do not help us evaluate the specification.
Your feedback now will help ensure that Stable means this mechanism works consistently across language implementations, tools, and workflow platforms.