Fix: don't adopt foreign global OTel parent for Judgment spans (regression from #749) - #752
Draft
claude[bot] wants to merge 1 commit into
Draft
Fix: don't adopt foreign global OTel parent for Judgment spans (regression from #749)#752claude[bot] wants to merge 1 commit into
claude[bot] wants to merge 1 commit into
Conversation
Decouple the two directions of context propagation that #749 conflated. READ direction: get_current_context() now returns Judgment's private runtime context by default, so Judgment mints independent root traces and no longer adopts a foreign ambient parent from the global OTel context (e.g. Google ADK / Vertex Agent Engine request spans). Adopting the ambient global parent is now strictly opt-in via _use_global_context, which defaults to False even after install_as_global_tracer_provider(). WRITE direction: preserved. Judgment's active span is still mirrored into the global context (new _mirror_active_span_to_global flag, enabled on global install) so external instrumentation keeps nesting under Judgment. attach_context/detach_context now dual-write to both the private runtime context and the global context so intra-Judgment nesting is preserved when reading from the private context. The customer interim mitigation (_use_global_context = False) is now the default and remains valid. Adds regression tests: a Judgment span created under an unsampled ambient foreign parent still records, exports, and is an independent Judgment root; intra-Judgment nesting is preserved; and the opt-in still restores adoption. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01KKT33Etr45m6SfJRsx3uUX
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Requested by Konark · Slack thread
📝 Summary
get_current_context()(READ) now returns Judgment's private runtime context by default → Judgment mints independent root traces again._use_global_context(now defaults toFalseeven after global install). The customer's_use_global_context = Falsemitigation is now the default and stays valid.Before / After (plain language)
Before: When judgeval is installed as the global OpenTelemetry provider inside a long-lived server that already has ambient OTel spans (Google ADK on Vertex Agent Engine), Judgment silently stops recording its own traces. Every Judgment span gets re-parented under the ambient foreign request span (orphaned — Judgment's backend never sees that root), or is dropped by the default
ParentBasedsampler when that ambient parent isn't sampled. A short standalone script works only because its global context is empty at span-creation time.After: Judgment creates its own independent root traces again by default. The outward-nesting behavior from #749 (external libraries nesting under Judgment's spans) is preserved. Adopting the ambient global parent is now explicit opt-in.
How
The bug was that #749 tied two opposite directions of propagation to a single
_use_global_contextflag that flipped toTrueoninstall_as_global_tracer_provider():Changes in
src/judgeval/trace/judgment_tracer_provider.py:_mirror_active_span_to_global(WRITE).install_as_global_tracer_provider()now sets this flag (not_use_global_context)._use_global_context(READ) now defaults toFalseand staysFalseafter global install; it is the explicit opt-in knob to restore full Fix: Install as global tracer provider controls otel context #749 read behavior (ambient-parent adoption).get_current_context()returns the private_runtime_contextunless_use_global_contextis set.attach_context()/detach_context()now dual-write: always attach to the private runtime context (so intra-Judgment nesting works when reading privately) and additionally attach to the global context when_should_write_to_global_context()is true (mirror on, or read-opt-in on). A small_DualContextTokentracks both tokens and unwinds them in reverse order.Regression test
src/tests/trace/test_tracer_provider.py→TestForeignParentDecoupling:test_span_under_unsampled_ambient_parent_still_exports_as_judgment_root: installs the global-provider WRITE state, attaches an unsampled foreignNonRecordingSpan(TraceFlags(0)) into the global OTel context, creates a Judgment span, and asserts it (a) is recording/sampled, (b) is exported via an in-memory collecting exporter, and (c) is an independent Judgment root — itstrace_iddiffers from the foreign parent's and itsparentisNone.test_intra_judgment_nesting_preserved: a Judgment child correctly parents under a Judgment parent (same trace, correct parent span id).test_opt_in_use_global_context_restores_adoption: with_use_global_context = True, Judgment reads the global context and does adopt the ambient parent (full Fix: Install as global tracer provider controls otel context #749 read behavior).TestGlobalContextBridgeto reflect the WRITE/READ split (install enables WRITE, not READ).Root cause
#749 made
install_as_global_tracer_provider()set_use_global_context = True, which causedget_current_context()to read the global OTel context and adopt whatever ambient (foreign, often unsampled) span was current as the parent of Judgment's spans.Open question for @abhi (sampler)
The sampler was intentionally left unchanged in this PR. My analysis suggested defaulting the inner
TracerProvider's sampler toALWAYS_ONas defense-in-depth (src/judgeval/trace/tracer.py~line 227, currentlysampler=sampler→ SDK defaultParentBased(root=ALWAYS_ON)), but this is deferred to you.Empirically verified via the regression test: with the decoupling fix alone, the unsampled-ambient-parent test passes — because Judgment spans become independent roots, and the default
ParentBased(root=ALWAYS_ON)samples roots regardless of the ambient parent's (unsampled) flags. ForcingALWAYS_ONon the inner provider would be belt-and-suspenders and is a separable follow-up for you to decide. No sampler change was needed to make the regression test green.Interim customer mitigation
Signal Advisors is applying
JudgmentTracerProvider.get_instance()._use_global_context = False. This PR makes that value the default, so the mitigation becomes a no-op (still valid and harmless).✅ Checklist
🤖 Generated with Claude Code
https://claude.ai/code/session_01KKT33Etr45m6SfJRsx3uUX
Generated by Claude Code