feat(app-server): propagate traces across tasks and core ops (#14387)

## Summary

This PR keeps app-server RPC request trace context alive for the full
lifetime of the work that request kicks off (e.g. for `thread/start`,
this is `app-server rpc handler -> tokio background task -> core op
submissions`). Previously we lose trace lineage once the request handler
returns or hands work off to background tasks.

This approach is especially relevant for `thread/start` and other RPC
handlers that run in a non-blocking way. In the near future we'll most
likely want to make all app-server handlers run in a non-blocking way by
default, and only queue operations that must operate in order (e.g.
thread RPCs per thread?), so we want to make sure tracing in app-server
just generally works.

Depends on https://github.com/openai/codex/pull/14300

**Before**
<img width="155" height="207" alt="image"
src="https://github.com/user-attachments/assets/c9487459-36f1-436c-beb7-fafeb40737af"
/>


**After**
<img width="299" height="337" alt="image"
src="https://github.com/user-attachments/assets/727392b2-d072-4427-9dc4-0502d8652dea"
/>

## What changed

- Keep request-scoped trace context around until we send the final
response or error, or the connection closes.
- Thread that trace context through detached `thread/start` work so
background startup stays attached to the originating request.
- Pass request trace context through to downstream core operations,
including:
  - thread creation
  - resume/fork flows
  - turn submission
  - review
  - interrupt
  - realtime conversation operations
- Add tracing tests that verify:
  - remote W3C trace context is preserved for `thread/start`
  - remote W3C trace context is preserved for `turn/start`
  - downstream core spans stay under the originating request span
  - request-scoped tracing state is cleaned up correctly
- Clean up shutdown behavior so detached background tasks and spawned
threads are drained before process exit.
This commit is contained in:
Owen Lin authored and GitHub committed 2026-03-11 20:18:31 -07:00
1 parent bf5e997b31
commit 5bc82c5b93
24 files changed
+1524 -308

No files matched your search

+14 -3
View File
@@ -1567,8 +1567,16 @@ impl App {
self.chat_widget.thread_name(),
);
self.shutdown_current_thread().await;
if let Err(err) = self.server.remove_and_close_all_threads().await {
tracing::warn!(error = %err, "failed to close all threads");
let report = self
.server
.shutdown_all_threads_bounded(Duration::from_secs(10))
.await;
if !report.submit_failed.is_empty() || !report.timed_out.is_empty() {
tracing::warn!(
submit_failed = report.submit_failed.len(),
timed_out = report.timed_out.len(),
"failed to close all threads"
);
}
let init = crate::chatwidget::ChatWidgetInit {
config,
@@ -1834,6 +1842,7 @@ impl App {
config.clone(),
target_session.path.clone(),
auth_manager.clone(),
None,
)
.await
.wrap_err_with(|| {
@@ -1871,6 +1880,7 @@ impl App {
config.clone(),
target_session.path.clone(),
false,
None,
)
.await
.wrap_err_with(|| {
@@ -2182,6 +2192,7 @@ impl App {
resume_config.clone(),
target_session.path.clone(),
self.auth_manager.clone(),
None,
)
.await
{
@@ -2250,7 +2261,7 @@ impl App {
if path.exists() {
match self
.server
.fork_thread(usize::MAX, self.config.clone(), path.clone(), false)
.fork_thread(usize::MAX, self.config.clone(), path.clone(), false, None)
.await
{
Ok(forked) => {