Use set_parent instead of thread-local context for traceparent
Replaces attach_traceparent/TraceparentGuard with context_from_traceparent,
letting callers set the parent directly on the span via
OpenTelemetrySpanExt::set_parent rather than manipulating thread-local
OTEL context state.

https://claude.ai/code/session_01Tbgz29e8A9KS4Bh94skkFX
change
commit d80ed875c3a0152df1586245952bfd82fc68334a
author Claude <noreply@anthropic.com>
date
parent 1d919d9a
diff --git a/quire-ci/Cargo.toml b/quire-ci/Cargo.toml
index 5e7328a..a99f364 100644
--- a/quire-ci/Cargo.toml
+++ b/quire-ci/Cargo.toml
@@ -19,3 +19,4 @@ tempfile = { workspace = true }
 thiserror = { workspace = true }
 tokio = { workspace = true, features = ["rt-multi-thread"] }
 tracing = { workspace = true }
+tracing-opentelemetry = { workspace = true }
diff --git a/quire-ci/src/main.rs b/quire-ci/src/main.rs
index 425050f..68d2cd1 100644
--- a/quire-ci/src/main.rs
+++ b/quire-ci/src/main.rs
@@ -18,6 +18,7 @@ use quire_core::ci::runtime::{Runtime, RuntimeError, RuntimeEvent, RuntimeHandle
 use quire_core::fennel::FennelError;
 use quire_core::secret::{Error as SecretError, Result as SecretResult, SecretRegistry};
 use quire_core::telemetry::{self, FmtMode, MietteLayer};
+use tracing_opentelemetry::OpenTelemetrySpanExt as _;
 use reqwest::header::{AUTHORIZATION, HeaderMap, HeaderValue};
 use std::sync::Arc;
 
@@ -364,14 +365,12 @@ fn main() -> Result<()> {
             // before the Sentry client flushes to the server.
             let _tracing_guard = telemetry::init_tracing(miette_layer, FmtMode::Plain)?;
 
-            // Attach the remote trace context so quire-ci spans appear
-            // as children of the orchestrator's span in Sentry.
-            let _cx_guard = sentry_ctx
-                .traceparent
-                .as_deref()
-                .map(telemetry::attach_traceparent);
-            let _run_span =
-                tracing::info_span!("quire.ci.run", sha = %meta.sha, r#ref = %meta.r#ref).entered();
+            let run_span =
+                tracing::info_span!("quire.ci.run", sha = %meta.sha, r#ref = %meta.r#ref);
+            if let Some(tp) = sentry_ctx.traceparent.as_deref() {
+                run_span.set_parent(telemetry::context_from_traceparent(tp));
+            }
+            let _run_span = run_span.entered();
 
             let registry = SecretRegistry::new(move |name| client.fetch_secret(name));
 
diff --git a/quire-core/src/telemetry.rs b/quire-core/src/telemetry.rs
index 3d0be4b..e3d14df 100644
--- a/quire-core/src/telemetry.rs
+++ b/quire-core/src/telemetry.rs
@@ -6,6 +6,7 @@ use std::sync::Arc;
 use miette::IntoDiagnostic;
 use opentelemetry::propagation::TextMapPropagator as _;
 use opentelemetry::trace::TracerProvider as _;
+use tracing_opentelemetry::OpenTelemetrySpanExt as _;
 use tracing_subscriber::EnvFilter;
 use tracing_subscriber::Layer;
 use tracing_subscriber::layer::SubscriberExt;
@@ -200,10 +201,6 @@ impl Drop for TracingGuard {
     }
 }
 
-/// Opaque guard that restores the previous OTEL context on drop.
-#[allow(dead_code)]
-pub struct TraceparentGuard(opentelemetry::ContextGuard);
-
 /// Extract the W3C traceparent for the currently active tracing span.
 /// Returns None when no OTEL span is active (e.g. no DSN, not yet entered a span).
 pub fn current_traceparent() -> Option<String> {
@@ -214,14 +211,13 @@ pub fn current_traceparent() -> Option<String> {
     carrier.remove("traceparent")
 }
 
-/// Inject a W3C traceparent into the current thread's OTEL context.
-/// The returned guard restores the previous context on drop.
-pub fn attach_traceparent(traceparent: &str) -> TraceparentGuard {
+/// Decode a W3C traceparent string into an OTEL context suitable for
+/// passing to [`tracing_opentelemetry::OpenTelemetrySpanExt::set_parent`].
+pub fn context_from_traceparent(traceparent: &str) -> opentelemetry::Context {
     let propagator = opentelemetry_sdk::propagation::TraceContextPropagator::new();
     let mut carrier = std::collections::HashMap::new();
     carrier.insert("traceparent".to_string(), traceparent.to_string());
-    let cx = propagator.extract(&carrier);
-    TraceparentGuard(cx.attach())
+    propagator.extract(&carrier)
 }
 
 /// Initialize the global tracing subscriber with `QUIRE_LOG`-driven filtering,