From 4cc8b24faa520068874db0a2d46fd0d4674f7a7b Mon Sep 17 00:00:00 2001 From: Adam Perry Date: Sat, 27 Jun 2026 00:56:24 +0000 Subject: [PATCH] core: trace executor skill discovery --- .../core-skills/src/loader/environment.rs | 7 +++++++ codex-rs/core/src/session/mod.rs | 1 + codex-rs/core/src/session/world_state.rs | 1 + codex-rs/exec-server/src/client.rs | 11 +++++++++- codex-rs/exec-server/src/rpc.rs | 21 ++++++++++++++++++- codex-rs/ext/skills/src/state.rs | 7 +++++++ 6 files changed, 46 insertions(+), 2 deletions(-) diff --git a/codex-rs/core-skills/src/loader/environment.rs b/codex-rs/core-skills/src/loader/environment.rs index 049b50058596..3d50bafe1fd3 100644 --- a/codex-rs/core-skills/src/loader/environment.rs +++ b/codex-rs/core-skills/src/loader/environment.rs @@ -143,6 +143,12 @@ pub struct EnvironmentSkillLoadOutcome { } /// Discovers skills without converting environment-owned paths to host paths. +#[tracing::instrument( + name = "skills.environment.load", + level = "info", + skip_all, + fields(skill_count = tracing::field::Empty) +)] pub async fn load_environment_skills_from_root( file_system: &dyn ExecutorFileSystem, root: &PathUri, @@ -235,6 +241,7 @@ pub async fn load_environment_skills_from_root( warnings: vec![format!("failed to walk skills root {root}: {error:#}")], }, }; + tracing::Span::current().record("skill_count", discovery.skills.len()); outcome.warnings.extend(discovery.warnings); if discovery.skills.is_empty() { return outcome; diff --git a/codex-rs/core/src/session/mod.rs b/codex-rs/core/src/session/mod.rs index 560dcf5985a4..3589687802fa 100644 --- a/codex-rs/core/src/session/mod.rs +++ b/codex-rs/core/src/session/mod.rs @@ -2833,6 +2833,7 @@ impl Session { /// This may refresh filesystem-derived state. Normal turns should call it only from /// `run_turn` and pass the result down; standalone request or history boundaries may capture /// their own step. + #[tracing::instrument(name = "step_context.capture", level = "info", skip_all)] pub(crate) async fn capture_step_context( self: &Arc, turn_context: Arc, diff --git a/codex-rs/core/src/session/world_state.rs b/codex-rs/core/src/session/world_state.rs index a78116d45751..89684e9e504e 100644 --- a/codex-rs/core/src/session/world_state.rs +++ b/codex-rs/core/src/session/world_state.rs @@ -7,6 +7,7 @@ use crate::context::world_state::WorldState; use codex_extension_api::WorldStateContributionInput; impl Session { + #[tracing::instrument(name = "world_state.build", level = "info", skip_all)] pub(crate) async fn build_world_state_for_step( &self, step_context: &StepContext, diff --git a/codex-rs/exec-server/src/client.rs b/codex-rs/exec-server/src/client.rs index 82412a58e60a..5590fd3b5eb5 100644 --- a/codex-rs/exec-server/src/client.rs +++ b/codex-rs/exec-server/src/client.rs @@ -1394,7 +1394,16 @@ mod tests { assert_eq!(session.process_id(), &process_id); let trace = server.await.expect("server task").expect("trace context"); - assert_eq!(trace, expected_trace); + let expected_traceparent = expected_trace + .traceparent + .as_deref() + .expect("parent traceparent"); + let traceparent = trace.traceparent.as_deref().expect("request traceparent"); + let expected_parts = expected_traceparent.split('-').collect::>(); + let parts = traceparent.split('-').collect::>(); + assert_eq!(parts[1], expected_parts[1]); + assert_ne!(parts[2], expected_parts[2]); + assert_eq!(trace.tracestate, expected_trace.tracestate); } async fn accept_websocket(listener: &TcpListener) -> WebSocketStream { diff --git a/codex-rs/exec-server/src/rpc.rs b/codex-rs/exec-server/src/rpc.rs index 46a0bd14e824..e762fe8b66c3 100644 --- a/codex-rs/exec-server/src/rpc.rs +++ b/codex-rs/exec-server/src/rpc.rs @@ -342,6 +342,16 @@ impl RpcClient { drain_pending(&self.pending).await; } + #[tracing::instrument( + name = "codex.exec_server.request", + level = "info", + skip_all, + fields( + otel.kind = "client", + otel.name = method, + method, + ) + )] pub(crate) async fn call(&self, method: &str, params: &P) -> Result where P: Serialize, @@ -803,6 +813,15 @@ mod tests { .expect("RPC response"); assert_eq!(response, serde_json::json!({})); let trace = server.await.expect("server task").expect("trace context"); - assert_eq!(trace, expected_trace); + let expected_traceparent = expected_trace + .traceparent + .as_deref() + .expect("parent traceparent"); + let traceparent = trace.traceparent.as_deref().expect("request traceparent"); + let expected_parts = expected_traceparent.split('-').collect::>(); + let parts = traceparent.split('-').collect::>(); + assert_eq!(parts[1], expected_parts[1]); + assert_ne!(parts[2], expected_parts[2]); + assert_eq!(trace.tracestate, expected_trace.tracestate); } } diff --git a/codex-rs/ext/skills/src/state.rs b/codex-rs/ext/skills/src/state.rs index 2f1a84c46b17..2094f344962c 100644 --- a/codex-rs/ext/skills/src/state.rs +++ b/codex-rs/ext/skills/src/state.rs @@ -66,6 +66,12 @@ impl SkillsThreadState { /// Environment availability only controls whether the root is projected into the current /// step; it never invalidates the cache. There is intentionally no filesystem watcher or /// content-based invalidation because selected environment roots are treated as stable. + #[tracing::instrument( + name = "skills.executor.catalog_snapshot", + level = "info", + skip_all, + fields(root_count = query.executor_roots.len()) + )] pub(crate) async fn executor_catalog_snapshot( &self, providers: &SkillProviders, @@ -157,6 +163,7 @@ impl SkillsThreadState { next_cache } + #[tracing::instrument(name = "skills.executor.catalog_root", level = "info", skip_all)] async fn executor_root_catalog( &self, providers: &SkillProviders,