Native OTel export omits originating-thread context for side conversations and title-generation helpers

Open 💬 0 comments Opened Jul 23, 2026 by J-H99

What issue are you seeing?

Native OTel logs and traces represent some Codex-created derived conversations as unrelated root conversations, even though the relevant lineage or purpose context is available in Codex before the telemetry export boundary.

This currently has at least two manifestations.

1\. Side conversations

The open-source side-conversation path explicitly knows that it is creating a side conversation:

  • fork_side_thread selects ForkPresentation::SideConversation.
  • It calls thread/fork with the source thread ID.
  • Core derives source_thread_id and passes it as forked_from_thread_id.
  • The app-server Thread response exposes the relationship as forkedFromId.

However, native OTel events for the derived conversation expose only that conversation's own conversation.id. SessionTelemetryMetadata and codex.conversation_starts do not carry forked_from_thread_id, parent_thread_id, thread_source, or an equivalent derived-conversation purpose.

The side-conversation path also currently passes ThreadSource::User, so its purpose is not preserved as a distinct thread source even before OTel export.

As a result, an OTLP consumer cannot determine that conversation B is a side conversation forked from visible conversation A.

2\. Title-generation helper conversations

This manifestation was previously reported in openai/codex#32708.

A title-generation helper receives its own conversation.id. The target visible thread ID is known when the generated name is applied through thread/name/set, whose public request type contains thread_id.

However:

  • The helper's native OTel events do not identify its purpose as title generation.
  • They do not identify the visible thread for which the title is being generated.
  • The app_server.request span records the RPC method and request ID, but not the target thread_id.

Therefore, the helper conversation cannot be deterministically correlated with the visible thread whose title it generated.

These are different creation paths, but they expose the same native OTel contract gap: Codex has the relevant derived-conversation context before export, but that context is not carried across the OTLP telemetry boundary.

This causes downstream observability systems to count side conversations and title-generation helpers as independent root sessions or leave them unattributed. Timestamp-, model-, prompt-, and ephemeral-state heuristics are not deterministic.

This report is not requesting high-cardinality thread IDs in OTel metrics. The relationship is needed in logs and traces only.

What steps can reproduce the bug?

Side conversation
  1. Configure Codex native OTel log and trace exporters. Keep otel.log_user_prompt = false.
  2. Start a regular conversation A.
  3. Open a side conversation from A and send a message, creating conversation B.
  4. Inspect codex.conversation_starts and the trace-safe events for B.
  5. Compare them with the fork information available through the app-server thread model.

Actual result:

  • B has its own conversation.id.
  • The app-server/core path retains A as the fork source.
  • Native OTel does not emit the relationship from B to A.
  • Native OTel does not identify B as a side conversation.
Title generation
  1. Start a new conversation in the Codex App.
  2. Wait for automatic title generation.
  3. Inspect native OTel events for the helper conversation and the thread/name/set request span.
  4. Attempt to determine which visible conversation received the generated title.

Actual result:

  • The helper has its own conversation.id.
  • Neither the helper events nor the request span provide a stable helper-to-visible-thread relationship.
  • The helper is not identified with a title-generation purpose.

What is the expected behavior?

Native OTel logs and traces should preserve enough context to classify a derived conversation and correlate it with its originating thread.

For example:

  • forked_from_thread_id or originating_thread_id
  • thread_source = "side_conversation" for side conversations
  • thread_source = "title_generation" for title-generation helpers

The exact field names are flexible. The important requirements are:

  • The derived conversation keeps its own conversation.id.
  • Its originating visible thread can be identified deterministically.
  • Its purpose can be distinguished from ordinary user-created root conversations.
  • The relationship remains available with otel.log_user_prompt = false.
  • No prompt text, generated title, or other user content needs to be exported.
  • Per-thread IDs do not need to be added to metrics.

Additional information

Environment available for reproduction:

  • Codex App: 26.715.10079.0
  • Platform: Microsoft Windows NT 10.0.26200.0 x64
  • Source snapshot inspected: openai/codex@4e0cee8030c4833baa6331b378101545accc9956

Relevant source evidence:

  • The side-conversation path explicitly selects SideConversation and sends a fork request with the source thread:<br>[https://github.com/openai/codex/blob/4e0cee8030c4833baa6331b378101545accc9956/codex-rs/tui/src/app_server_session.rs#L607-L659](<https://github.com/openai/codex/blob/4e0cee8030c4833baa6331b378101545accc9956/codex-rs/tui/src/app_server_session.rs#L607-L659>)
  • Core derives and retains the fork source as forked_from_thread_id:<br>[https://github.com/openai/codex/blob/4e0cee8030c4833baa6331b378101545accc9956/codex-rs/core/src/thread_manager.rs#L1038-L1080](<https://github.com/openai/codex/blob/4e0cee8030c4833baa6331b378101545accc9956/codex-rs/core/src/thread_manager.rs#L1038-L1080>)
  • The public app-server Thread model exposes session_id, forked_from_id, parent_thread_id, and thread_source:<br>[https://github.com/openai/codex/blob/4e0cee8030c4833baa6331b378101545accc9956/codex-rs/app-server-protocol/src/protocol/v2/thread_data.rs#L170-L219](<https://github.com/openai/codex/blob/4e0cee8030c4833baa6331b378101545accc9956/codex-rs/app-server-protocol/src/protocol/v2/thread_data.rs#L170-L219>)
  • Native OTel session metadata and conversation_starts do not include those relationship or purpose fields:<br>[https://github.com/openai/codex/blob/4e0cee8030c4833baa6331b378101545accc9956/codex-rs/otel/src/events/session_telemetry.rs#L82-L100](<https://github.com/openai/codex/blob/4e0cee8030c4833baa6331b378101545accc9956/codex-rs/otel/src/events/session_telemetry.rs#L82-L100>)<br>[https://github.com/openai/codex/blob/4e0cee8030c4833baa6331b378101545accc9956/codex-rs/otel/src/events/session_telemetry.rs#L474-L510](<https://github.com/openai/codex/blob/4e0cee8030c4833baa6331b378101545accc9956/codex-rs/otel/src/events/session_telemetry.rs#L474-L510>)
  • thread/name/set receives the target thread ID, while app-server request tracing records the method and request ID rather than request parameters:<br>[https://github.com/openai/codex/blob/4e0cee8030c4833baa6331b378101545accc9956/codex-rs/app-server-protocol/src/protocol/v2/thread.rs#L739-L745](<https://github.com/openai/codex/blob/4e0cee8030c4833baa6331b378101545accc9956/codex-rs/app-server-protocol/src/protocol/v2/thread.rs#L739-L745>)<br>[https://github.com/openai/codex/blob/4e0cee8030c4833baa6331b378101545accc9956/codex-rs/app-server/src/app_server_tracing.rs#L24-L113](<https://github.com/openai/codex/blob/4e0cee8030c4833baa6331b378101545accc9956/codex-rs/app-server/src/app_server_tracing.rs#L24-L113>)

Related issue:

  • openai/codex#32708 documents the title-generation-helper manifestation:<br>[https://github.com/openai/codex/issues/32708](<https://github.com/openai/codex/issues/32708>)

View original on GitHub ↗