Skip to content
Merged
Show file tree
Hide file tree
Changes from 13 commits
Commits
Show all changes
38 commits
Select commit Hold shift + click to select a range
b8b0da5
fix(a2a): reject encoded responses before body reads
markstuart-oai Nov 5, 2026
02aba47
fix(a2a): preserve serialized structured results
markstuart-oai Nov 5, 2026
291d975
Bound authentication recovery across transport fallback
markstuart-oai Nov 5, 2026
ce6124e
Read the realtime pairing test request count from the token server
markstuart-oai Nov 5, 2026
e1a40fe
Merge the SessionStore runtime and allow the API coverage fixture to …
markstuart-oai Nov 5, 2026
fe13809
Integrate current session storage and verify sampling authentication …
markstuart-oai Nov 5, 2026
231f7a6
Merge current worker quota example updates
markstuart-oai Nov 5, 2026
1e311e3
chore: merge Codex package extraction into A2A response guard
markstuart-oai Nov 5, 2026
d16fcb3
Integrate the owned-child tracing fix with the A2A adapter
markstuart-oai Nov 5, 2026
f567ccd
Integrate current tracing ownership fix into authentication recovery
markstuart-oai Nov 5, 2026
37e5fd0
Merge current native OpenSSL fixture isolation
markstuart-oai Nov 5, 2026
d3a9cc4
Await the Core carrier close handshake before sampling reconnect
markstuart-oai Nov 5, 2026
dfbee78
chore: integrate current child tracing policy into A2A response guard
markstuart-oai Nov 5, 2026
61d83aa
Integrate the macOS executor HTTP probe fix
markstuart-oai Nov 5, 2026
b5c2b46
Retain the native workflow policy through queued child submissions
markstuart-oai Nov 5, 2026
b182783
Use the owning turn for executor approval tracing
markstuart-oai Nov 5, 2026
f367048
Assert the denied child command stops before executor dispatch
markstuart-oai Nov 5, 2026
8206687
Exercise independent ambient and instrumented trace contexts
markstuart-oai Nov 5, 2026
48aa8a1
Integrate native child tracing with A2A structured results
markstuart-oai Nov 5, 2026
39806a9
Merge child tracing isolation repair into A2A response encoding
markstuart-oai Nov 5, 2026
2e4cbf6
Merge owned child tracing context into authentication retry
markstuart-oai Nov 5, 2026
44f6e78
Preserve child callback ownership across queued submissions and execu…
markstuart-oai Nov 5, 2026
64bc380
Integrate native child callback ownership with A2A structured results
markstuart-oai Nov 5, 2026
a580fde
Merge current v1 child tracing ownership fix
markstuart-oai Nov 5, 2026
82d821a
Merge current child callback ownership integration
markstuart-oai Nov 5, 2026
b723a82
Keep W3C trace ancestry separate from workflow export settings
markstuart-oai Nov 5, 2026
f58b643
Integrate native trace-policy isolation with A2A structured results
markstuart-oai Nov 5, 2026
d5b24dc
Carry the admitted caller context through native child operations
markstuart-oai Nov 5, 2026
2fec101
Preserve admitted caller context for A2A structured results
markstuart-oai Nov 5, 2026
05c403a
Bind cancellation to the admitted turn context
markstuart-oai Nov 5, 2026
6fcf2b3
Verify export routing through the public tracing API
markstuart-oai Nov 5, 2026
b287ca0
Integrate explicit native trace ownership into authentication retry
markstuart-oai Nov 5, 2026
6d01bd0
fix: wait for settled native child admission
markstuart-oai Nov 5, 2026
1875314
Merge commit 'b8a51306ccceea0da5553703fef14a5afe3f8fb4' into codex/v1…
markstuart-oai Nov 5, 2026
c5971ad
fix: integrate queued tracing into response encoding
markstuart-oai Nov 5, 2026
a560d1e
fix: integrate response encoding and queued tracing into structured r…
markstuart-oai Nov 5, 2026
15b9bf5
fix: integrate A2A and corrected child tracing into auth recovery
markstuart-oai Nov 5, 2026
8a22c44
Merge current v1 into terminal authentication retry
markstuart-oai Nov 5, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions .changeset/owned-child-tracing-context.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
---
'@openai/agents': patch
'@openai/agents-codex': patch
---

fix: preserve each queued native submission's trace and privacy settings when another workflow is active
6 changes: 6 additions & 0 deletions .changeset/quiet-terminal-authentication.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
---
'@openai/agents': patch
'@openai/agents-codex': patch
---

fix: stop sampling after final HTTP 401 or 403 responses and share bounded credential recovery across WebSocket-to-HTTP fallback while preserving final provider metadata.
Original file line number Diff line number Diff line change
@@ -0,0 +1,348 @@
diff --git a/codex-rs/core/src/agent/control_tests.rs b/codex-rs/core/src/agent/control_tests.rs
index 6ac6135..140dd3d 100644
--- a/codex-rs/core/src/agent/control_tests.rs
+++ b/codex-rs/core/src/agent/control_tests.rs
@@ -5835,6 +5835,7 @@ async fn tree_shutdown_preserves_sibling_completion_activity_with_full_parent_qu
op: Op::ReloadUserConfig,
trace: None,
turn_extension_init: None,
+ trace_policy: None,
parent_turn_id: None,
root_turn_id: None,
residency_guard: None,
diff --git a/codex-rs/core/src/codex_delegate.rs b/codex-rs/core/src/codex_delegate.rs
index 6caab5e..28b5147 100644
--- a/codex-rs/core/src/codex_delegate.rs
+++ b/codex-rs/core/src/codex_delegate.rs
@@ -287,6 +287,7 @@ pub(crate) async fn run_codex_thread_one_shot(
op: Op::Shutdown {},
turn_extension_init: None,
trace: None,
+ trace_policy: None,
parent_turn_id: None,
root_turn_id: None,
residency_guard: None,
diff --git a/codex-rs/core/src/codex_delegate_tests.rs b/codex-rs/core/src/codex_delegate_tests.rs
index 0ca83da..78fb323 100644
--- a/codex-rs/core/src/codex_delegate_tests.rs
+++ b/codex-rs/core/src/codex_delegate_tests.rs
@@ -155,6 +155,7 @@ async fn forward_ops_preserves_submission_trace_context() {
),
tracestate: Some("vendor=state".to_string()),
}),
+ trace_policy: None,
parent_turn_id: Some("parent-turn".to_string()),
root_turn_id: Some("root-turn".to_string()),
residency_guard: None,
diff --git a/codex-rs/core/src/codex_thread.rs b/codex-rs/core/src/codex_thread.rs
index f9c5f8e..d1c66d4 100644
--- a/codex-rs/core/src/codex_thread.rs
+++ b/codex-rs/core/src/codex_thread.rs
@@ -576,6 +576,7 @@ impl CodexThread {
op: Op::SuspendTurnAndShutdown { reply },
turn_extension_init: None,
trace: current_span_w3c_trace_context(),
+ trace_policy: None,
parent_turn_id: None,
root_turn_id: None,
residency_guard: None,
diff --git a/codex-rs/core/src/session/handlers.rs b/codex-rs/core/src/session/handlers.rs
index f6fcf14..e190c5f 100644
--- a/codex-rs/core/src/session/handlers.rs
+++ b/codex-rs/core/src/session/handlers.rs
@@ -6,7 +6,8 @@ use crate::realtime_conversation::handle_speech as handle_realtime_conversation_
use crate::realtime_conversation::handle_start as handle_realtime_conversation_start;
use crate::realtime_conversation::handle_text as handle_realtime_conversation_text;
use async_channel::Receiver;
-use codex_otel::set_parent_from_w3c_trace_context;
+use codex_otel::context_from_w3c_trace_context;
+use codex_otel::set_parent_from_context;
use tracing::Instrument;
use tracing::debug_span;
use tracing::info_span;
@@ -773,13 +774,18 @@ pub(super) fn submission_dispatch_span(sub: &Submission) -> tracing::Span {
codex.op = op_name
),
};
- if let Some(trace) = sub.trace.as_ref()
- && !set_parent_from_w3c_trace_context(&dispatch_span, trace)
- {
- warn!(
- submission.id = sub.id.as_str(),
- "ignoring invalid submission trace carrier"
- );
+ if let Some(trace) = sub.trace.as_ref() {
+ if let Some(mut parent) = context_from_w3c_trace_context(trace) {
+ if let Some(policy) = &sub.trace_policy {
+ parent = parent.with_value(policy.clone());
+ }
+ set_parent_from_context(&dispatch_span, parent);
+ } else {
+ warn!(
+ submission.id = sub.id.as_str(),
+ "ignoring invalid submission trace carrier"
+ );
+ }
}
dispatch_span
}
diff --git a/codex-rs/core/src/session/mod.rs b/codex-rs/core/src/session/mod.rs
index 0187a60..413a2e6 100644
--- a/codex-rs/core/src/session/mod.rs
+++ b/codex-rs/core/src/session/mod.rs
@@ -97,7 +97,6 @@ use codex_network_proxy::NetworkProxyAuditMetadata;
use codex_network_proxy::normalize_host;
use codex_otel::auth_storage::AuthStorageOriginator;
use codex_otel::current_span_trace_id;
-use codex_otel::current_span_w3c_trace_context;
use codex_otel::set_parent_from_w3c_trace_context;
use codex_prompts::render_model_instructions;
use codex_protocol::ResponseUsageMetadata;
@@ -1018,6 +1017,7 @@ impl SessionIo {
op,
turn_extension_init,
trace,
+ trace_policy: None,
parent_turn_id,
root_turn_id,
residency_guard,
@@ -1029,7 +1029,7 @@ impl SessionIo {
/// Use sparingly: prefer `submit()` so submission IDs are generated consistently.
pub(crate) async fn submit_with_id(&self, mut sub: Submission) -> CodexResult<()> {
if sub.trace.is_none() {
- sub.trace = current_span_w3c_trace_context();
+ (sub.trace, sub.trace_policy) = codex_otel::sdk_trace::submission_trace();
}
self.tx_sub
.send(sub)
@@ -1063,6 +1063,7 @@ impl SessionIo {
},
trace,
turn_extension_init,
+ trace_policy: None,
parent_turn_id: None,
root_turn_id: None,
residency_guard: None,
@@ -1089,6 +1090,7 @@ impl SessionIo {
},
trace,
turn_extension_init,
+ trace_policy: None,
parent_turn_id: None,
root_turn_id: None,
residency_guard: None,
diff --git a/codex-rs/core/src/session/step_activation_tests.rs b/codex-rs/core/src/session/step_activation_tests.rs
index 68e2cf7..5ad38f4 100644
--- a/codex-rs/core/src/session/step_activation_tests.rs
+++ b/codex-rs/core/src/session/step_activation_tests.rs
@@ -497,6 +497,7 @@ fn settings_submission(
reply,
},
trace: None,
+ trace_policy: None,
parent_turn_id: None,
root_turn_id: None,
residency_guard: None,
diff --git a/codex-rs/core/src/session/submission.rs b/codex-rs/core/src/session/submission.rs
index 413e187..a4a34bd 100644
--- a/codex-rs/core/src/session/submission.rs
+++ b/codex-rs/core/src/session/submission.rs
@@ -14,6 +14,8 @@ pub(crate) struct Submission {
pub turn_extension_init: Option<ExtensionDataInit>,
/// Optional W3C trace carrier propagated across async submission handoffs.
pub trace: Option<W3cTraceContext>,
+ /// Local workflow policy owned by this queued submission until dispatch.
+ pub trace_policy: Option<std::sync::Arc<codex_otel::sdk_trace::ScopePolicy>>,
pub parent_turn_id: Option<String>,
pub root_turn_id: Option<String>,
/// Keeps a V2 recipient resident until this submission is handled or dropped.
diff --git a/codex-rs/core/src/session/tests.rs b/codex-rs/core/src/session/tests.rs
index d5ee490..3744bc3 100644
--- a/codex-rs/core/src/session/tests.rs
+++ b/codex-rs/core/src/session/tests.rs
@@ -6285,6 +6285,7 @@ async fn standalone_settings_invalidate_continuation_before_delivering_acceptanc
reply: Some(reply),
},
trace: None,
+ trace_policy: None,
parent_turn_id: None,
root_turn_id: None,
residency_guard: None,
@@ -8366,6 +8367,7 @@ fn submission_dispatch_span_prefers_submission_trace_context() {
turn_extension_init: None,
id: "sub-1".into(),
op: Op::Interrupt,
+ trace_policy: None,
parent_turn_id: None,
root_turn_id: None,
residency_guard: None,
@@ -8396,6 +8398,7 @@ fn submission_dispatch_span_uses_debug_for_realtime_audio() {
item_id: None,
},
}),
+ trace_policy: None,
parent_turn_id: None,
root_turn_id: None,
residency_guard: None,
@@ -8755,6 +8758,7 @@ async fn spawn_task_turn_span_inherits_dispatch_trace_context() {
turn_extension_init: None,
id: "sub-1".into(),
op: Op::Interrupt,
+ trace_policy: None,
parent_turn_id: None,
root_turn_id: None,
residency_guard: None,
diff --git a/codex-rs/otel/src/sdk_trace.rs b/codex-rs/otel/src/sdk_trace.rs
index 6d56fba..9127d03 100644
--- a/codex-rs/otel/src/sdk_trace.rs
+++ b/codex-rs/otel/src/sdk_trace.rs
@@ -542,13 +542,21 @@ pub fn end_trace_span(key: &str, encoded: &str) -> Result<(), String> {
pub fn context(key: &str) -> Result<Context, String> {
Ok(resolve(key)?.context.clone())
}
+pub fn submission_trace() -> (
+ Option<codex_protocol::protocol::W3cTraceContext>,
+ Option<Arc<ScopePolicy>>,
+) {
+ let active = crate::trace_context::current_span_context();
+ (
+ crate::trace_context::context_w3c_trace_context(&active),
+ policy_for_context(&active),
+ )
+}
+
pub fn current_context_token() -> Option<String> {
- // Native lifecycle hooks may run in an instrumented tracing span without
- // an attached OpenTelemetry Context. Prefer that exact span's carrier.
- let context = crate::trace_context::current_span_w3c_trace_context()
- .and_then(|trace| crate::trace_context::context_from_w3c_trace_context(&trace))
- .filter(|context| policy_for_context(context).is_some())
- .unwrap_or_else(|| restore_policy(Context::current()));
+ // Native lifecycle hooks can inherit private values on the instrumented
+ // span. A W3C round trip would discard those values before policy lookup.
+ let context = crate::trace_context::current_span_context();
let policy = policy_for_context(&context)?;
let registration = retain_policy(context.span().span_context(), policy);
let handle = Arc::new(Handle {
@@ -979,6 +987,98 @@ mod context_lease_tests {
use super::*;
use opentelemetry_sdk::trace::{InMemorySpanExporter, Sampler, SdkTracerProvider};

+ #[test]
+ fn unowned_instrumented_span_never_borrows_ambient_workflow_policy() {
+ use opentelemetry::trace::TracerProvider as _;
+ use tracing_opentelemetry::OpenTelemetrySpanExt as _;
+ use tracing_subscriber::prelude::*;
+
+ const CHILD: &str = "CODEX_SDK_UNOWNED_INSTRUMENTED_CONTEXT_CHILD";
+ if std::env::var_os(CHILD).is_none() {
+ let status = std::process::Command::new(std::env::current_exe().unwrap())
+ .args([
+ "--exact",
+ "sdk_trace::context_lease_tests::unowned_instrumented_span_never_borrows_ambient_workflow_policy",
+ "--nocapture",
+ ])
+ .env(CHILD, "1")
+ .status()
+ .unwrap();
+ assert!(status.success());
+ return;
+ }
+
+ let provider = SdkTracerProvider::builder()
+ .with_sampler(Sampler::AlwaysOn)
+ .build();
+ global::set_tracer_provider(provider.clone());
+ let subscriber = tracing_subscriber::registry().with(
+ tracing_opentelemetry::layer().with_tracer(provider.tracer("unowned-context-fixture")),
+ );
+ let _subscriber = tracing::subscriber::set_default(subscriber);
+ configure_pipeline(true);
+
+ let workflow: Value = serde_json::from_str(
+ &start_trace_span(
+ r#"{"name":"unrelated-B","kind":"workflow","trace_id":"bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb","disabled":false,"include_sensitive_data":true}"#,
+ )
+ .unwrap(),
+ )
+ .unwrap();
+ let workflow_key = workflow["handle"].as_str().unwrap();
+ let unrelated = context(workflow_key).unwrap();
+ let unrelated_policy = policy_for_context(&unrelated).unwrap();
+ assert!(unrelated_policy.include_sensitive_data);
+ assert!(!unrelated_policy.disabled);
+
+ // A is a valid instrumented native span with an explicit, unowned parent.
+ // B stays attached on this thread, but must not supply A's workflow policy.
+ let carrier = codex_protocol::protocol::W3cTraceContext {
+ traceparent: Some("00-aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa-1111111111111111-01".to_owned()),
+ tracestate: None,
+ };
+ let explicit = crate::context_from_w3c_trace_context(&carrier).unwrap();
+ assert!(policy_for_context(&explicit).is_none());
+ let native = tracing::info_span!(parent: None, "unowned-native-A");
+ let _ = native.set_parent(explicit);
+ let captured = {
+ let _entered = native.enter();
+ let _attached_unrelated = unrelated.attach();
+ let active = crate::trace_context::current_span_context();
+ assert!(active.span().span_context().is_valid());
+ assert_eq!(
+ active.span().span_context().trace_id().to_string(),
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"
+ );
+ assert!(policy_for_context(&active).is_none());
+ assert_eq!(
+ Context::current()
+ .span()
+ .span_context()
+ .trace_id()
+ .to_string(),
+ "bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb"
+ );
+ ContextLease::capture()
+ };
+
+ // Save only public tracing flags for the failure message, then release any
+ // wrongly captured lease before asserting. No credential or payload is used.
+ let borrowed = captured.as_ref().map(|lease| {
+ serde_json::from_str::<Value>(&trace_span_context(lease.token()).unwrap()).unwrap()
+ });
+ drop(captured);
+ drop(native);
+ end_trace_span(workflow_key, r#"{"status":"ok"}"#).unwrap();
+ release_trace_span(workflow_key);
+ provider.shutdown().unwrap();
+
+ assert!(
+ borrowed.is_none(),
+ "unowned instrumented A borrowed unrelated B's SDK context: {borrowed:?}"
+ );
+ }
+
#[test]
fn captured_leases_retain_intermediate_identity_until_last_owner_releases() {
const CHILD: &str = "CODEX_SDK_CONTEXT_LEASE_IDENTITY_CHILD";
diff --git a/codex-rs/otel/src/trace_context.rs b/codex-rs/otel/src/trace_context.rs
index e1c6277..4d9cf8c 100644
--- a/codex-rs/otel/src/trace_context.rs
+++ b/codex-rs/otel/src/trace_context.rs
@@ -27,8 +27,16 @@ static TRACESTATE_ENTRIES: OnceLock<RwLock<BTreeMap<String, BTreeMap<String, Str
OnceLock::new();

pub fn current_span_w3c_trace_context() -> Option<W3cTraceContext> {
- span_w3c_trace_context(&Span::current())
- .or_else(|| context_w3c_trace_context(&Context::current()))
+ context_w3c_trace_context(&current_span_context())
+}
+
+pub(crate) fn current_span_context() -> Context {
+ let active = Span::current().context();
+ if active.span().span_context().is_valid() {
+ active
+ } else {
+ Context::current()
+ }
}

pub fn span_w3c_trace_context(span: &Span) -> Option<W3cTraceContext> {
Loading
Loading