Skip to content

envcar: ignore non-normalized env names on Get and Keys#9112

Merged
pellared merged 32 commits into
open-telemetry:mainfrom
pellared:env-var-get-keys-dont-normalize
Jun 25, 2026
Merged

envcar: ignore non-normalized env names on Get and Keys#9112
pellared merged 32 commits into
open-telemetry:mainfrom
pellared:env-var-get-keys-dont-normalize

Conversation

@pellared

@pellared pellared commented Jun 9, 2026

Copy link
Copy Markdown
Member

Follows open-telemetry/opentelemetry-specification#5144 and open-telemetry/opentelemetry-specification#5163.

Also related to the follow-up caching/stability discussion in open-telemetry/opentelemetry-specification#5166.

This updates go.opentelemetry.io/contrib/propagators/envcar so reads from the current process environment follow the Environment Variables as Context Propagation Carriers key-normalization model without pre-populating a normalized copy of the whole environment.

Changes

  • Update Carrier.Get to normalize the requested key and perform a direct lookup of the matching normalized environment variable.
  • Update Carrier.Keys to return only environment variable names that are already normalized, so non-normalized names such as traceparent are ignored instead of being normalized into TRACEPARENT.
  • Remove the carrier's cached environment snapshot so Get and Keys use the same live-read observation model and avoid extra state/enumeration overhead.
  • Add normalized helper coverage and update empty-key normalization so normalize("") returns _, matching the documented valid environment-name conversion.
  • Expand GoDoc documentation to describe envcar as process-boundary startup input: applications should extract from the environment once during initialization and keep using the extracted context instead of repeatedly treating environment variables as a live parent-context source.
  • Update the child-process example so it injects context from a real SDK span.

Rationale

The PR discussion raised two related concerns:

  • A cache can make repeated Get calls stable if a process mutates its environment later.
  • Avoiding a cache removes carrier state and avoids enumerating the whole environment for common extraction paths.

This PR keeps the Go implementation lightweight and documents the intended usage instead: extract environment context once near application startup, then pass/use that extracted context. It also keeps Get and Keys consistent by making both read directly from the current environment rather than mixing a live Get with snapshot-style Keys.

@pellared
pellared requested a review from Copilot June 9, 2026 09:37
@pellared pellared changed the title envcar: ignore non-normalized env names on Carrier.Get and Carrier.Keys envcar: ignore non-normalized env names on Get and Keys Jun 9, 2026

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Updates the envcar propagator’s environment variable handling to ignore non-normalized environment variable names when reading from the current process, aligning behavior with the Environment Carrier specification discussion (opentelemetry-specification#5143).

Changes:

  • Add a normalized helper to detect already-normalized environment variable names.
  • Update Carrier to cache only already-normalized environment variables on first read (affecting Get/Keys behavior).
  • Adjust tests, examples, docs, and changelog to reflect the new behavior.

Reviewed changes

Copilot reviewed 7 out of 7 changed files in this pull request and generated 3 comments.

Show a summary per file
File Description
propagators/envcar/normalize.go Adds normalized helper used to filter env var names.
propagators/envcar/normalize_test.go Adds unit test + benchmark coverage for normalized.
propagators/envcar/doc.go Minor doc comment punctuation/link formatting tweak.
propagators/envcar/carrier.go Changes env cache population to ignore non-normalized env var names; updates docs.
propagators/envcar/carrier_test.go Updates/extends tests to validate ignoring non-normalized env var names.
propagators/envcar/carrier_example_test.go Removes example output assertions (reduces executable-doc coverage).
CHANGELOG.md Documents the changed behavior under Unreleased.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread propagators/envcar/carrier_example_test.go
Comment thread propagators/envcar/carrier_example_test.go
Comment thread propagators/envcar/carrier_test.go
@codecov

codecov Bot commented Jun 9, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 83.8%. Comparing base (df9c7a8) to head (f15acc3).

Additional details and impacted files

Impacted file tree graph

@@          Coverage Diff          @@
##            main   #9112   +/-   ##
=====================================
  Coverage   83.8%   83.8%           
=====================================
  Files        195     195           
  Lines      16077   16082    +5     
=====================================
+ Hits       13476   13481    +5     
  Misses      2129    2129           
  Partials     472     472           
Files with missing lines Coverage Δ
propagators/envcar/carrier.go 100.0% <100.0%> (ø)
propagators/envcar/normalize.go 100.0% <100.0%> (ø)
🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
@pellared
pellared marked this pull request as ready for review June 9, 2026 09:46
@pellared
pellared requested a review from a team as a code owner June 9, 2026 09:46
@pellared
pellared marked this pull request as draft June 9, 2026 09:46
@pellared
pellared requested a review from Copilot June 9, 2026 09:46
@github-actions
github-actions Bot requested a review from Joibel June 9, 2026 09:47

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 7 out of 7 changed files in this pull request and generated 2 comments.

Comment thread propagators/envcar/carrier_test.go Outdated
Comment thread propagators/envcar/carrier_test.go
@pellared
pellared marked this pull request as ready for review June 9, 2026 10:45
Comment thread propagators/envcar/carrier_example_test.go
Comment thread propagators/envcar/carrier_example_test.go
@pellared
pellared requested a review from Joibel June 9, 2026 14:24

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 7 out of 7 changed files in this pull request and generated 2 comments.

Comment thread propagators/envcar/carrier_test.go
Comment thread propagators/envcar/carrier_test.go

@Joibel Joibel left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks good, thank you

cijothomas pushed a commit to cijothomas/opentelemetry-specification that referenced this pull request Jun 12, 2026
…etry#5144)

Fixes
open-telemetry#5143

Prototypes: 
- open-telemetry/opentelemetry-go-contrib#9112
- open-telemetry/opentelemetry-cpp#4141
- open-telemetry/opentelemetry-java#8474
- open-telemetry/opentelemetry-js#6774
- open-telemetry/opentelemetry-python#5289
- open-telemetry/opentelemetry-swift-core#83

### Rationale

The previous wording required environment variable carriers to normalize
every key present in the carrier before matching a requested propagation
key.

This PR removes that requirement for two reasons:

- **Efficiency**: extraction should not require reading every
environment variable
- **Determinism**: if multiple environment variable names normalize to
the same key, selecting one is ambiguous and implementation-dependent.

Instead, carriers normalize the key requested by the propagator and read
only the corresponding normalized environment variable name. `Keys`
similarly returns only environment variable names that are already
normalized.

This keeps injection behavior unchanged while making extraction
predictable and avoiding unnecessary environment enumeration.

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 7 out of 7 changed files in this pull request and generated no new comments.

return c.values[normalize(key)]
// Get returns the value associated with the normalized key.
// It reads the normalized key directly from the current process environment.
func (*Carrier) Get(key string) string {

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why the change back from store on first call? The Carrier was not supposed to (potentially) return different values for the same key, see the original PR for discussions on this.

@pellared pellared Jun 20, 2026

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please check this discussion #9112 (comment)

This is also a related PR open-telemetry/opentelemetry-specification#5166

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Long story short the appropriate usage is to use this carrier once near application startup.

Caching and enumeration causes unnecessary overhead that we want to avoid.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I unresolved the mentioned thread so that you can navigate to it

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Does that mean that Keys and Get can respond differently (if used contra to that startup expectation). Shouldn't we drop the store behaviour for Keys too? If the expectation is basically only one call to Keys ever then the store is pretty pointless.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I get wanting to avoid enumerating all environment variables for efficiency, however Get now reads directly from the env vars.

The mutability of env vars is a concern of mine and I worry that reading directly from env vars without a cache could lead to unpredictable runtime bugs (eg. when an instrumented process emits several spans using the env var context as parent context and the vars were changed in between). Even instantiating the carrier once during startup would not protect against this.

The more I think about it, the more I want to add normative language to the spec like: Get MUST NOT return different values when called with the same argument implying the use of a cache.

@pellared pellared Jun 20, 2026

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@Joibel, @kamphaus, thanks a lot for your questions/concerns. These are exactly the discussions that we need to have before going RC.

Does that mean that Keys and Get can respond differently (if used contra to that startup expectation). Shouldn't we drop the store behaviour for Keys too? If the expectation is basically only one call to Keys ever then the store is pretty pointless.

@Joibel, I agree that Keys and Get should have the same observation model. If the expected usage is extracting once near startup, then Keys will typically be called at most once for that extraction path (e.g. when using https://pkg.go.dev/go.opentelemetry.io/contrib/propagators/ot which is the only known propagator that uses Keys method).

Removing the Keys cache also seems consistent with the current intended usage, and I think it would avoid the confusing case where Get is live but Keys is a snapshot. It also looks fine for your vendored carrier usage here: https://github.com/argoproj/argo-workflows/blob/4cb7eb4e89299595ba8fddd3b7f9d95872c3b39d/workflow/executor/tracing/tracing.go#L26.

Addressed aeaccb0

Just to double-check. Would you see any benefit from caching Get and Keys, or would it just be unnecessary state/overhead?

Even instantiating the carrier once during startup would not protect against this.

@kamphaus, agreed that construction alone would not protect against later environment mutation. What I meant is that applications should extract from the environment once during startup and then use the extracted context, rather than repeatedly using the process environment as a live parent-context source.

Addressed ef3ca09

The more I think about it, the more I want to add normative language to the spec like: Get MUST NOT return different values when called with the same argument implying the use of a cache.

@kamphaus, I do not think we have evidence yet that this is something which needs to be normative.

For OTel Go, C++ and Rust, we usually care very much about any overhead, I think it is fine if we can expect users to not call Get more than once for the same argument.

At the same time, I think that for some languages it may be fine to cache. But maybe even for these languages it should be opt-in as it cause overhead that users may not want.

At this point of time, I lean towards the non-normative proposal from open-telemetry/opentelemetry-specification#5166

I do not take this lightly and I plan to further discuss this with maintainers of other language implementations.

@Joibel, WDYT?

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I do not take this lightly and I plan to also discuss this with other language maintainers.

I agree that this needs to be discussed with other language maintainers and thank you for taking this seriously.

Might I suggest in the meantime to document in the godoc that applications using envcar/Carrier should cache the context based on it?

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Might I suggest in the meantime to document in the godoc that applications using envcar/Carrier should cache the context based on it?

I did this before I read your comment 😉

PTAL ef3ca09

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

PTAL ef3ca09

And I reviewed it before seeing this.

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 7 out of 7 changed files in this pull request and generated no new comments.

@pellared
pellared requested a review from Copilot June 20, 2026 19:13
@pellared pellared added this to the v1.45.0 milestone Jun 20, 2026

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 7 out of 7 changed files in this pull request and generated no new comments.

pellared added a commit to pellared/opentelemetry-specification that referenced this pull request Jun 22, 2026
…try#5163)

Issue discovered by @MrAlias in
open-telemetry/opentelemetry-go-contrib#9112 (comment)

Also see
open-telemetry#5163 (comment)

Prototypes: 
-
open-telemetry/opentelemetry-go-contrib@0fedf62
- open-telemetry/opentelemetry-dotnet#7424
- open-telemetry/opentelemetry-swift-core#86
- open-telemetry/opentelemetry-python#5328
- open-telemetry/opentelemetry-js#6827
- open-telemetry/opentelemetry-java#8481

For values to be fully portable across POSIX-compliant systems and shell
utilities, the standard dictates that environment variable names must
consist solely of uppercase letters, digits, and underscores (_), and
must not begin with a digit. While some individual implementations might
tolerate unusual characters, an empty string lacks any valid characters
entirely and cannot act as a recognizable identifier.

I verified that on Ubuntu it is not possible to set a env var with an
empty name.

---------

Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
pellared added a commit to pellared/opentelemetry-specification that referenced this pull request Jun 23, 2026
…ive (open-telemetry#5165)

### Motivation

The Environment Variables as Context Propagation Carriers specification
used normative wording in the "Operational Guidance" section for
application-level usage advice, such as when applications read
environment variables or how they prepare child process environments.

That made the section read like it was adding specification requirements
for application behavior, even though the intent is guidance for how
environment variable carriers are typically used.

This came up in discussion with @dashpole and also I mentioned this in
open-telemetry/opentelemetry-go-contrib#9112 (comment)

Here is a simple example how the languages can be document some
operational guidance:
open-telemetry/opentelemetry-go-contrib@5b1606f

---------

Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
Co-authored-by: Tyler Yahn <MrAlias@users.noreply.github.com>

@MrAlias MrAlias left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks, this looks good overall. I left one non-blocking question about the example-only SDK dependency.

Comment thread propagators/envcar/go.mod Outdated
@pellared
pellared merged commit 9fcc177 into open-telemetry:main Jun 25, 2026
30 checks passed
pellared added a commit to pellared/opentelemetry-specification that referenced this pull request Jun 29, 2026
…y#5179)

## Changes

This PR removes the non-normative implementation guidance that suggested
environment variable propagation carriers could cache environment
variables or individual `Get` lookup results.

The environment carrier specification already frames environment
variables as process-startup input: applications should extract context
during initialization, then continue using the extracted `Context`. With
that usage model, the specification does not need to recommend caching
as an implementation pattern. Implementations can still use owned
storage or explicit snapshots where their language API requires it, but
that should be a language-specific design choice rather than general
spec guidance.

Non-caching implementations so far:
- Go:
https://github.com/open-telemetry/opentelemetry-go-contrib/blob/main/propagators/envcar/carrier.go
- Swift:
https://github.com/open-telemetry/opentelemetry-swift-core/blob/main/Sources/OpenTelemetrySdk/Trace/Propagation/EnvironmentContextPropagator.swift
- JavaScript:
open-telemetry/opentelemetry-js#6853

## Motivation

This follows the same direction as the discussion that led to open-telemetry#5143 and
open-telemetry#5144. The concern raised in the open-telemetry#5142 review was that environment
carrier extraction should not require inspecting every environment
variable. Full-carrier enumeration is inefficient, especially when
OpenTelemetry is present but no environment propagation variables are
configured.

open-telemetry#5144 addressed that by requiring `Get` to normalize the requested
propagator key and read only that normalized environment variable name.
`Keys` remains the operation that may enumerate keys, and it returns
only names that are already normalized.

The caching examples added in open-telemetry#5166 can be read as encouraging
implementations to reintroduce hidden first-use state, such as:

- loading the whole environment into a cache during initialization or on
first use, or
- caching individual `Get` results by normalized key.

That guidance is unnecessary and can work against the intent of
open-telemetry#5143/open-telemetry#5144:

- Standard extraction typically reads only a small set of keys such as
`TRACEPARENT`, `TRACESTATE`, and `BAGGAGE`.
- Repeated extraction from the process environment is not the intended
hot path; users should extract once at startup and reuse the resulting
`Context`.
- Whole-environment snapshots can cost more than direct lookup for the
common extraction path.
- Hidden caches can make later environment changes invisible in a way
that is surprising unless the API explicitly describes itself as a
snapshot.

This also aligns with
[Performance](https://opentelemetry.io/community/mission/#we-value-performance),
which is a core OpenTelemetry Engineering Value.

The Go PR reviews showed the same issue in an implementation: review
feedback called out that `Get` should do a direct lookup of the
normalized key and leave enumeration to `Keys`, instead of enumerating
and storing the process environment on the first `Get`.
-
open-telemetry/opentelemetry-go-contrib#9112 (comment)
-
open-telemetry/opentelemetry-go-contrib#9112 (comment)

Also see:
open-telemetry#5142 (comment)

## Compatibility

This PR does not add or remove normative requirements. It only removes
non-normative guidance that could steer language implementations toward
default caching.

Language implementations remain free to use internal storage when
required by their API shape, and users can still choose explicit
snapshots or caller-provided maps where that is the clearer model. The
specification should avoid presenting caching as the recommended default
for environment carrier extraction.

## Related

- open-telemetry#5142
- open-telemetry#5143
- open-telemetry#5144
- open-telemetry#5166
- open-telemetry#5040
- open-telemetry#4771
pellared added a commit to pellared/opentelemetry-specification that referenced this pull request Jun 29, 2026
I think adding this note may be helpful for implementation and
troubleshooting.

Personally, even though I was fully aware of the behavior on Windows, I
still forgot about it at least twice while working on the
implementations for OTel JS
(open-telemetry/opentelemetry-js#6853 (comment))
and OTel Go
(open-telemetry/opentelemetry-go-contrib#9112).

In OTel Go, I even decided to include this in method documentation.

```go
// Get normalizes key and returns the corresponding value from the current
// process environment. It returns an empty string if the normalized
// environment variable is unset or set to an empty value.
//
// On platforms with case-insensitive environment lookup, such as Windows, the
// lookup may match an environment variable whose name differs from the
// normalized key only by case.
func (*Carrier) Get(key string) string {
	return os.Getenv(normalize(key))
}
```
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

6 participants