feat(app-server): propagate app-server trace context into core (#13368)

### Summary
Propagate trace context originating at app-server RPC method handlers ->
codex core submission loop (so this includes spans such as `run_turn`!).
This implements PR 2 of the app-server tracing rollout.

This also removes the old lower-level env-based reparenting in core so
explicit request/submission ancestry wins instead of being overridden by
ambient `TRACEPARENT` state.

### What changed
- Added `trace: Option<W3cTraceContext>` to codex_protocol::Submission
- Taught `Codex::submit()` / `submit_with_id()` to automatically capture
the current span context when constructing or forwarding a submission
- Wrapped the core submission loop in a submission_dispatch span
parented from Submission.trace
- Warn on invalid submission trace carriers and ignore them cleanly
- Removed the old env-based downstream reparenting path in core task
execution
- Stopped OTEL provider init from implicitly attaching env trace context
process-wide
- Updated mcp-server Submission call sites for the new field

Added focused unit tests for:
- capturing trace context into Submission
- preferring `Submission.trace` when building the core dispatch span

### Why
PR 1 gave us consistent inbound request spans in app-server, but that
only covered the transport boundary. For long-running work like turns
and reviews, the important missing piece was preserving ancestry after
the request handler returns and core continues work on a different async
path.

This change makes that handoff explicit and keeps the parentage rules
simple:
- app-server request span sets the current context
- `Submission.trace` snapshots that context
- core restores it once, at the submission boundary
- deeper core spans inherit naturally

That also lets us stop relying on env-based reparenting for this path,
which was too ambient and could override explicit ancestry.
This commit is contained in:
Owen Lin authored and GitHub committed 2026-03-04 01:03:45 +00:00
1 parent 0fbd84081b
commit 52521a5e40
12 files changed
+489 -196

No files matched your search

-22
View File
@@ -7,7 +7,6 @@ use crate::trace_context::context_from_trace_headers;
use gethostname::gethostname;
use opentelemetry::Context;
use opentelemetry::KeyValue;
use opentelemetry::context::ContextGuard;
use opentelemetry::global;
use opentelemetry::trace::TracerProvider as _;
use opentelemetry_appender_tracing::layer::OpenTelemetryTracingBridge;
@@ -28,7 +27,6 @@ use opentelemetry_sdk::trace::BatchSpanProcessor;
use opentelemetry_sdk::trace::SdkTracerProvider;
use opentelemetry_sdk::trace::Tracer;
use opentelemetry_semantic_conventions as semconv;
use std::cell::RefCell;
use std::env;
use std::error::Error;
use std::sync::OnceLock;
@@ -43,10 +41,6 @@ const HOST_NAME_ATTRIBUTE: &str = "host.name";
const TRACEPARENT_ENV_VAR: &str = "TRACEPARENT";
const TRACESTATE_ENV_VAR: &str = "TRACESTATE";
static TRACEPARENT_CONTEXT: OnceLock<Option<Context>> = OnceLock::new();
thread_local! {
static TRACEPARENT_GUARD: RefCell<Option<ContextGuard>> = const { RefCell::new(None) };
}
pub struct OtelProvider {
pub logger: Option<SdkLoggerProvider>,
pub tracer_provider: Option<SdkTracerProvider>,
@@ -113,10 +107,6 @@ impl OtelProvider {
global::set_tracer_provider(provider);
global::set_text_map_propagator(TraceContextPropagator::new());
}
if tracer.is_some() {
attach_traceparent_context();
}
Ok(Some(Self {
logger,
tracer_provider,
@@ -176,18 +166,6 @@ pub fn traceparent_context_from_env() -> Option<Context> {
.clone()
}
fn attach_traceparent_context() {
TRACEPARENT_GUARD.with(|guard| {
let mut guard = guard.borrow_mut();
if guard.is_some() {
return;
}
if let Some(context) = traceparent_context_from_env() {
*guard = Some(context.attach());
}
});
}
fn load_traceparent_context() -> Option<Context> {
let traceparent = env::var(TRACEPARENT_ENV_VAR).ok()?;
let tracestate = env::var(TRACESTATE_ENV_VAR).ok();
-8
View File
@@ -15,7 +15,6 @@ use crate::metrics::names::WEBSOCKET_EVENT_COUNT_METRIC;
use crate::metrics::names::WEBSOCKET_EVENT_DURATION_METRIC;
use crate::metrics::names::WEBSOCKET_REQUEST_COUNT_METRIC;
use crate::metrics::names::WEBSOCKET_REQUEST_DURATION_METRIC;
use crate::otel_provider::traceparent_context_from_env;
use crate::sanitize_metric_tag_value;
use chrono::SecondsFormat;
use chrono::Utc;
@@ -41,7 +40,6 @@ use std::time::Duration;
use std::time::Instant;
use tokio::time::error::Elapsed;
use tracing::Span;
use tracing_opentelemetry::OpenTelemetrySpanExt;
pub use crate::OtelEventMetadata;
pub use crate::OtelManager;
@@ -92,12 +90,6 @@ impl OtelManager {
}
}
pub fn apply_traceparent_parent(&self, span: &Span) {
if let Some(context) = traceparent_context_from_env() {
let _ = span.set_parent(context);
}
}
pub fn record_responses(&self, handle_responses_span: &Span, event: &ResponseEvent) {
handle_responses_span.record("otel.name", OtelManager::responses_type(event));