From 6508273b1e2820ae58f2c9d8b347f54de8681ca7 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Mon, 26 Jan 2026 13:41:47 +0000 Subject: [PATCH 1/3] Initial plan From 7103e1a78df7cf2e6669c7d5a0f3de1ae064c631 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Mon, 26 Jan 2026 13:45:19 +0000 Subject: [PATCH 2/3] Add unit test for start_observe with external parent span Co-authored-by: CorentinGS <43623834+CorentinGS@users.noreply.github.com> --- tests/observability/test_context_managers.py | 54 ++++++++++++++++++++ 1 file changed, 54 insertions(+) diff --git a/tests/observability/test_context_managers.py b/tests/observability/test_context_managers.py index a0b88fd..c61d001 100644 --- a/tests/observability/test_context_managers.py +++ b/tests/observability/test_context_managers.py @@ -572,3 +572,57 @@ async def test_async_observe_has_in_trace_attribute(setup_tracing): assert root_span._span.attributes.get("basalt.in_trace") is True async with AsyncObserve(kind=ObserveKind.GENERATION, name="async_child") as child_span: assert child_span._span.attributes.get("basalt.in_trace") is True + + +def test_start_observe_with_external_parent_span(setup_tracing): + """ + Test that start_observe treats itself as a Basalt root when there's + a non-Basalt parent span (e.g., from FastAPI, httpx, or other instrumentation). + + This verifies the scenario where: + 1. An external (non-Basalt) span exists as the current span + 2. ROOT_SPAN_CONTEXT_KEY is not set (indicating no Basalt trace) + 3. start_observe is called + 4. The new span should be treated as a Basalt root + 5. ROOT_SPAN_CONTEXT_KEY should be attached + """ + from opentelemetry import trace + from opentelemetry.sdk.trace import TracerProvider + + from basalt.observability import StartObserve + + # Get or create a tracer provider + provider = trace.get_tracer_provider() + if not isinstance(provider, TracerProvider): + provider = TracerProvider() + trace.set_tracer_provider(provider) + + # Create an external (non-Basalt) parent span to simulate FastAPI/httpx + external_tracer = provider.get_tracer("external.instrumentation") + external_span = external_tracer.start_span("external_http_request") + + # Attach the external span to the context WITHOUT setting ROOT_SPAN_CONTEXT_KEY + # This simulates a non-Basalt parent span (e.g., from FastAPI or httpx) + external_context = trace.set_span_in_context(external_span) + token = otel_context.attach(external_context) + + try: + # Verify preconditions: we have a parent span but no Basalt root + assert trace.get_current_span() == external_span + assert otel_context.get_value(ROOT_SPAN_CONTEXT_KEY) is None + + # Use start_observe, which should treat itself as a Basalt root + # because the parent is not a Basalt span + with StartObserve(name="test_basalt_root", feature_slug="test_feature") as span: + # Assert that this span is treated as a Basalt root + assert span._span.attributes.get("basalt.root") is True + assert span._span.attributes.get("basalt.in_trace") is True + + # Assert that ROOT_SPAN_CONTEXT_KEY is now set in the context + root_span_from_context = otel_context.get_value(ROOT_SPAN_CONTEXT_KEY) + assert root_span_from_context is not None + assert root_span_from_context == span._span + finally: + # Clean up: end the external span and detach the context + external_span.end() + otel_context.detach(token) From f85282bd16db523f669aed7b2c79e6c173f5ae73 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Mon, 26 Jan 2026 13:46:26 +0000 Subject: [PATCH 3/3] Refactor test to address code review feedback Co-authored-by: CorentinGS <43623834+CorentinGS@users.noreply.github.com> --- tests/observability/test_context_managers.py | 11 ++--------- 1 file changed, 2 insertions(+), 9 deletions(-) diff --git a/tests/observability/test_context_managers.py b/tests/observability/test_context_managers.py index c61d001..a98489e 100644 --- a/tests/observability/test_context_managers.py +++ b/tests/observability/test_context_managers.py @@ -3,6 +3,7 @@ import pytest from opentelemetry import context as otel_context +from opentelemetry import trace from opentelemetry.trace import Span from basalt.observability.context_managers import ( @@ -586,18 +587,10 @@ def test_start_observe_with_external_parent_span(setup_tracing): 4. The new span should be treated as a Basalt root 5. ROOT_SPAN_CONTEXT_KEY should be attached """ - from opentelemetry import trace - from opentelemetry.sdk.trace import TracerProvider - from basalt.observability import StartObserve - # Get or create a tracer provider - provider = trace.get_tracer_provider() - if not isinstance(provider, TracerProvider): - provider = TracerProvider() - trace.set_tracer_provider(provider) - # Create an external (non-Basalt) parent span to simulate FastAPI/httpx + provider = trace.get_tracer_provider() external_tracer = provider.get_tracer("external.instrumentation") external_span = external_tracer.start_span("external_http_request")