## What changed
Add optional `contentTokenCount` metadata to `item/completed` notifications, carrying the producer-supplied token count for each completed assistant message in default and plan modes. Counts include any sampled footer and are `null` when unavailable.
Keep this telemetry separate from stored thread items and omit it from subsequent model requests. Update the protocol schemas and generated TypeScript and Python types.
## Testing
Add coverage for per-message counts, including zero, in default and plan modes; missing and malformed metadata; backward-compatible notification decoding; and exclusion from subsequent model requests.
GitOrigin-RevId: 35973037a42650fa2d7569da8e88cb81215cf971
## Why
A bundled reviewer entry does not establish that a custom gateway offers that model. Guardian should fall back to the parent model when the reviewer is neither advertised by the provider nor explicitly configured.
## What changed
Track model metadata origins as `Bundled`, `Provider`, or `ExplicitConfig`, including across catalog merges and cache reloads. Exclude bundled entries from Guardian reviewer selection unless `auto_review_model_override` is set. Continue selecting reviewers from provider and explicitly configured catalogs.
## Testing
Add coverage for gateway reviewer selection with each catalog origin, plus origin tracking across cache reloads, empty provider responses, and authentication changes.
GitOrigin-RevId: f8bf865d406e56ee0ade90087b55f2e33b98181e
## What changed
- Add `permissions.approved_command_prefix_saved` to model messages and use valid plain-text headings in saved-prefix notices, with or without full permissions instructions. Reject empty, oversized, control-character, and markup-like headings.
- Keep `Approved command prefix saved:` as the leading marker for older clients and recognize saved-prefix content kinds when trimming rolled-back context.
- Shut down analytics test exporters asynchronously and serialize workspace-switching tests that share authentication environment state.
## Testing
Extend coverage for heading validation, sparse wire values, single-notice emission, rollback with and without metadata, and saved-prefix behavior across model switches.
GitOrigin-RevId: a70175deca64a6339abac2fa69af970f470e3f4f
## What changed
- Add nullable `startedAtMs` and `completedAtMs` fields to `ThreadItem` in live notifications and turn history. Values are Unix milliseconds when recorded.
- Preserve recorded item times through history reconstruction, pagination, and resume, including command start times recorded during approval.
- Separate the Rust item payload into `ThreadItemPayload` while keeping the JSON payload flat, and update consumers, schemas, and generated TypeScript and Python types.
## Testing
Extend coverage for timestamp serialization, history reconstruction, paginated reads and resume, and persisted Python SDK items.
GitOrigin-RevId: ecbcee95af52db7c807b46a6e9deb30e5fba1f80
## Why
Disabling OpenAI analytics also disabled explicitly configured OTLP metrics collectors. Users should be able to export metrics to their own collectors independently of `analytics.enabled`.
## What changed
Apply the analytics preference only to the built-in `statsig` metrics exporter. Keep custom OTLP HTTP and gRPC exporters enabled regardless of that preference, and use `otel.metrics_exporter = "none"` to disable metrics export. Clarify these settings in config documentation and the schema.
## Testing
Cover exporter selection across explicit and default analytics preferences. Update app-server and daemon telemetry tests to verify custom metrics export with analytics disabled, and worktree tests to disable metrics through the exporter setting.
GitOrigin-RevId: 0c61f0f01e2b694db642ac7e7fd1dac9965c7d96
## What changed
- Use the standard shell executor and remove the zsh fork escalation runtime, `codex-execve-wrapper`, and `shell-escalation` crate.
- Ignore `shell_zsh_fork` and `unified_exec_zsh_fork`, emit migration notices for legacy configuration, and warn when ignoring managed requirements for these flags.
- Keep the active session's `apply_patch` alias directory readable under restricted filesystem permissions.
## Testing
Add coverage for legacy configuration startup and shell execution, migration notice rendering, active alias directory selection, and nested `apply_patch` execution while other files in `CODEX_HOME` remain unreadable.
GitOrigin-RevId: 43af461fea32b4980550ee7237effd2bc0646ed8
## Why
Usage limit errors discarded the server-selected window responsible for the limit, leaving turn and compaction analytics unable to distinguish five-hour limits from weekly limits.
## What changed
Preserve optional `limit_window_minutes` from usage limit error responses in `UsageLimitReachedError` and report it as `usage_limit_window_minutes` in turn and compaction events. Accept unsigned integers that fit in `u16`; treat missing, null, malformed, or out-of-range values as unknown. Events for other error kinds report `null`.
## Testing
Add coverage for HTTP and wrapped WebSocket error mapping, analytics serialization and reduction, and app-server propagation to turn and local/remote compaction events. Cases include five-hour and weekly windows, missing or invalid values, and unrelated errors.
GitOrigin-RevId: a32066a007559d91a3c868a756bfd0f976777176
## Why
Saved model selections can outlive their catalog entries. Removing GPT-5.4 needs to preserve a migration path without applying it to other providers that may still support the same model slug.
## What changed
- Remove `gpt-5.4` from the bundled catalog and `openai.gpt-5.4` from the Bedrock catalogs, including GovCloud.
- Add fallback migration prompts to GPT-6 Sol for saved GPT-5.4 selections on OpenAI and Amazon Bedrock. Scope fallback migrations, including the existing GPT-5.4 Mini migration, to their owning providers while honoring catalog metadata.
- Update tests that depend on bundled GPT-5.4 metadata to use GPT-5.5 or explicit model overrides.
## Testing
Extend migration tests to cover catalog metadata precedence, acknowledged targets, missing or hidden replacements, and preserving the provider while saving the replacement model and its default reasoning effort. Update Bedrock catalog assertions and picker snapshots.
GitOrigin-RevId: f739795da423b9622db2a416be6c52b01b8e7a71
## Why
Command execution analytics lack the observed sandbox backend. Older execution servers may omit this information, so an unknown backend must remain distinguishable from an explicitly unsandboxed process.
## What changed
- Carry optional `sandbox_type` metadata through command execution items and emit its metric tag as `sandbox_backend` in analytics.
- Propagate the observed backend through unified execution completion and failure events, including asynchronous exits.
- Preserve missing backend information as `None` and mark executed user shell commands as `Some(SandboxType::None)`.
- Exclude the metadata from serialized command items and generated schemas and TypeScript types.
## Testing
Extend tests for missing and explicit executor backends, backend propagation on failed execution, and analytics payloads, including platform-specific backend tags in app-server integration coverage.
GitOrigin-RevId: 178fe46824a6194910802ec02168ed40ee0b2019
## What changed
Add `active_plugin_ids_at_turn_start` to turn analytics, combining active host plugins with selected plugin packages. Prefer remote plugin IDs when present and otherwise use validated package IDs. Sort and deduplicate the inventory.
Report `null` for unknown inventories, invalid IDs, IDs longer than 128 bytes, or inventories exceeding 512 distinct IDs; preserve `[]` for an observed empty inventory. Keep the first received inventory even when later resolved configuration updates arrive.
## Testing
Add coverage for ID selection and validation, deduplication, size limits, serialization, and preservation of the first inventory. Extend app-server tests to check inventories after plugin reconciliation and with selected plugins, including turns without explicit plugin mentions.
GitOrigin-RevId: 8a0de912e2570a902cd05bfc918dc8e6ff8b8527
## Why
Model requests need the selected workspace's routing constraints before sending content. A missing cached route cannot establish that a custom ChatGPT-auth destination is independent of the workspace.
## What changed
- Register the account processor as the workspace routing resolver for Responses HTTP, compaction, and WebSockets, enabling origin selection and routing headers while preserving API paths and rejecting routed HTTP redirects.
- Share concurrent discovery by auth generation, workspace, and backend configuration. Recover from discovery-time `401` responses while allowing token refreshes for the same auth owner.
- Require successful discovery before classifying custom destinations as independent, and require a new thread when a workspace-bound session's bootstrap origin changes.
- Refresh managed requirements using retained session configuration without fetching thread configuration again. Return typed routing errors without account or backend details.
## Testing
Extend coverage for discovery-time token refresh, retained provider definitions under managed requirements, and specific invalid-routing error categories.
GitOrigin-RevId: 090cda70daa91ee8acfbc973699d028111e54125
## Why
Command execution and plugin measurement events lack model and reasoning-effort labels. Attribution needs to reflect the step that invoked the command, even when model settings change before a background process finishes.
## What changed
- Add `model_slug` and `reasoning_effort` to command execution and plugin measurement analytics.
- Capture model context from resolved step settings and carry it through execution, approval, Guardian review, and plugin metrics collection.
- Preserve the first command-start model context when subsequent start notifications arrive.
- Keep the carried context out of serialized protocol items and generated schemas.
## Testing
Extend analytics tests to cover model switches before invocation and during background execution, default reasoning effort, Guardian-denied commands, and repeated start notifications retaining the original model context.
GitOrigin-RevId: af90e1c0d39bab625f2e89786085b61a9b96c0ce
## What changed
- Add `originator` to plugin measurement inputs and emitted analytics events.
- Populate it from the turn originator when recording plugin measurements.
- Extend analytics tests to verify the originator is preserved in event payloads.
GitOrigin-RevId: 9998f829760222fc690dc8a10324794b5da27220
## What changed
- Emit `codex_guardian_v2_classification` events with the outcome, risk
level, duration, model, and thread attribution.
- Emit `codex_guardian_v2_fast_decision` events when Guardian V2 approves an
action without a full review.
- Add `guardian_v2_enabled` to turn analytics, accounting for review scope
and model requirements.
## Testing
- Extend the Guardian V2 app-server coverage to verify event payloads,
attribution, enablement state, and scoped event emission.
GitOrigin-RevId: 0805975f8e8f9d954aa1058ea8c60545a090ae74
## Why
Trusted Guardian reviewer sessions can emit events without an app-server listener, leaving their turns and tool usage absent from analytics.
## What changed
- Translate Guardian session lifecycle and canonical tool item events into analytics notifications while excluding messages, reasoning, and review content.
- Preserve Guardian lineage during prewarming even when inherited client metadata is not yet available, without emitting an incomplete thread initialization event.
- Limit direct session tracking to trusted Guardian children so resumed threads that have an app-server listener do not report duplicate events.
## Testing
- Cover completed, failed, and interrupted Guardian turns, tool counts and timing, lineage, content exclusion, prewarmed lineage, and resumed-thread deduplication.
GitOrigin-RevId: 70ace5f7ed30a7e3b6a9d6097c8b2a6a326dc4f7
## What changed
- Record collaboration tool analytics for `send_message`, `followup_task`,
`interrupt_agent`, and `list_agents`, including receiver attribution, duration,
failures, and interrupted calls.
- Include these calls in per-turn subagent tool counts while keeping the existing
`subAgentActivity` output unchanged and omitting additional public collaborator
tool items.
- Extend the collaboration tool schemas with the new tool variants and the
`interrupted` terminal status.
## Testing
- Add an app-server integration test covering successful and failed calls,
receiver attribution, duration, prompt omission, activity items, and turn
counts.
GitOrigin-RevId: e73eaa05c508261e5fc97e7d08d7881a31e04e22
## What changed
- Remove the legacy `shell_command` handler and runtime, leaving `exec_command`
and `write_stdin` as the shell execution tools.
- Treat legacy `shell_command` model metadata as `unified_exec`, and normalize
legacy user opt-outs so they do not disable command execution. Managed feature
requirements and `shell_tool` can still disable it.
- Preserve shell approvals, sandboxing, zsh-fork support, and output truncation
through the unified execution path.
## Testing
- Cover legacy configuration and model-metadata compatibility.
- Exercise unified shell execution, approvals, truncation, and `apply_patch`
serialization across the app-server and core test suites.
GitOrigin-RevId: 5c2fd6164fc3519cdae4944cb9db276b8467311c
## What changed
- Resolve manifest-declared metric operations against the executor filesystem for remote plugin commands.
- Create the measurement sidecar in an executor-native, owner-private temporary directory, stream its bounded output back for validation, and clean up the directory afterward.
- Extend the exec-server protocol with the executor temporary directory and atomic private-directory creation.
## Testing
- Cover remote unified-exec measurements for foreground and background commands.
- Verify private directories use owner-only permissions on Unix and fail closed on unsupported platforms.
GitOrigin-RevId: dbbd0a84717b91237fc5728e510e18994eb46dd4
## What changed
- Add end-to-end tests for curated plugin measurements through classic shell execution and unified exec.
- Cover unified background commands whose measurements arrive after the turn completes.
- Verify command attribution and measurement payloads, including values, dimensions, execution IDs, and thread, turn, and item IDs.
GitOrigin-RevId: 866103aafef8d3454123c40aee7041912ad0b7fc
## Why
Desktop Work threads and regular Codex threads can share the same
app-server connection. App-server analytics currently copy
`product_client_id` from connection metadata for every thread-scoped
event, so Work thread activity is attributed to the Desktop connection
instead of the thread's resolved originator. This prevents analytics
from distinguishing the two products on a shared connection.
## What changed
- Publish the resolved originator after a thread is materialized,
covering new, resumed, forked, and subagent threads.
- Store that originator in the analytics reducer's existing per-thread
state.
- Override only `app_server_client.product_client_id` for thread, turn,
tool, review, goal, guardian, and compaction events while preserving the
connection's client name, version, and transport metadata.
- Fall back to the connection-wide product client ID when a thread has
no originator override.
- Preserve persisted originators in thread initialization analytics for
resume and fork flows.
## Validation
- `just test -p codex-analytics
thread_originator_overrides_shared_connection_across_thread_events
subagent_events_keep_thread_originator_with_explicit_turn_connection`
- `just test -p codex-app-server
turn_start_tracks_thread_originator_in_analytics
thread_start_tracks_thread_initialized_analytics
thread_fork_tracks_thread_initialized_analytics
thread_resume_tracks_thread_initialized_analytics`
- `just test -p codex-core thread_manager`
## Why
- Currently, there is no analytics event for `/goal` behavior
- Existing events cannot identify goal execution or its resulting
outcome
- The original update in
[#26182](https://github.com/openai/codex/pull/26182) was implemented
before `/goal` moved into `codex-goal-extension`.
## What Changed
- Adds `codex_goal_event` serialization and enrichment to
`codex-analytics`
- Emits goal events from the canonical `codex-goal-extension` mutation
and accounting paths:
- `created` when a new logical goal is persisted
- `usage_accounted` when cumulative goal usage is persisted
- `status_changed` when the stored goal status changes
- `cleared` when the goal is deleted
- Preserves causal `turn_id` for turn driven events and uses null
attribution for external or idle lifecycle events
- Changes goal deletion to return the deleted row so `cleared` retains
the stable goal ID
## Event Details
Includes standard analytics metadata along with goal specific fields:
- `goal_id`: Stable ID stored in the local SQLite goal row and shared
across the goal's events
- `event_kind`: Observed operation (see the 4 lifecycle events cited in
the above bullet)
- `goal_status`: Resulting or last stored status: `active`, `paused`,
`blocked`, `usage_limited`, etc.
- `has_token_budget`: Indicates whether a token budget is configured
- `turn_id`: Causal turn ID, or null when no causal turn exists
- `cumulative_tokens_accounted`: Cumulative tokens on `usage_accounted`
events; null otherwise
- `cumulative_time_accounted_seconds`: Cumulative active time on
`usage_accounted` events; null otherwise
## Validation
- `just test -p codex-analytics -p codex-state -p codex-goal-extension`
- `just test -p codex-core -E 'test(/goal/)'`
- `just test -p codex-app-server`
- `cargo build -p codex-analytics -p codex-core -p codex-state -p
codex-app-server`
## Why
- Runtime analytics events report `thread_id`, which identifies the
individual thread emitting an event
- They don't report `session_id`, which identifies the shared session
for a root thread and its subagent threads
- Emitting both identifiers allows analytics to group related activity
## What Changed
- Adds `session_id` to relevant analytics events (thread_initalized,
turn, turn_steer, compaction, guardian_review)
- Tracks each thread's session ID in the analytics reducer so subsequent
thread scoped events emit the same value
- Carries the shared session ID through subagent initialization
## Verification
- `just test -p codex-analytics` validates event payloads and subagent
session grouping.
- Focused `codex-app-server` tests validate session IDs for thread,
turn, and steer events.
- Focused `codex-core` tests validate root and subagent session ID
propagation.
## Summary
- make `thread_source` an explicit optional thread-level field on
`thread/start`, `thread/fork`, and returned thread payloads
- persist `thread_source` in rollout/session metadata so resumed live
threads retain the original value
- replace the old best-effort `session_source` -> `thread_source`
mapping with an explicit caller-supplied analytics classification
## Why
Before this change, analytics `thread_source` was populated by a
best-effort mapping from `session_source`. `session_source` describes
the runtime/client surface, not the actual thread-level origin, so that
projection was not accurate enough to distinguish cases such as `user`,
`subagent`, `memory_consolidation`, and future thread origins reliably.
Making `thread_source` explicit keeps one thread-level analytics field
while letting callers provide the real classification directly instead
of recovering it indirectly from `session_source`.
## Impact
For new analytics events, `thread_source` now reflects the explicit
thread-level classification supplied by the caller rather than an
inferred value derived from `session_source`. Existing protocol fields
remain optional; callers that omit `threadSource` now produce `null`
instead of a best-effort inferred value.
## Validation
- `just write-app-server-schema`
- `cargo test -p codex-analytics -p codex-core -p
codex-app-server-protocol --no-run`
- `cargo test -p codex-app-server-protocol
generated_ts_optional_nullable_fields_only_in_params`
- `cargo test -p codex-analytics
thread_initialized_event_serializes_expected_shape`
- `cargo test -p codex-core
resume_stopped_thread_from_rollout_preserves_thread_source`
## Why
This finishes the config-type move out of `codex-core` by removing the
temporary compatibility shim in `codex_core::config::types`. Callers now
depend on `codex-config` directly, which keeps these config model types
owned by the config crate instead of re-expanding `codex-core` as a
transitive API surface.
## What Changed
- Removed the `codex-rs/core/src/config/types.rs` re-export shim and the
`core::config::ApprovalsReviewer` re-export.
- Updated `codex-core`, `codex-cli`, `codex-tui`, `codex-app-server`,
`codex-mcp-server`, and `codex-linux-sandbox` call sites to import
`codex_config::types` directly.
- Added explicit `codex-config` dependencies to downstream crates that
previously relied on the `codex-core` re-export.
- Regenerated `codex-rs/core/config.schema.json` after updating the
config docs path reference.
## Why
`codex-core` was re-exporting APIs owned by sibling `codex-*` crates,
which made downstream crates depend on `codex-core` as a proxy module
instead of the actual owner crate.
Removing those forwards makes crate boundaries explicit and lets leaf
crates drop unnecessary `codex-core` dependencies. In this PR, this
reduces the dependency on `codex-core` to `codex-login` in the following
files:
```
codex-rs/backend-client/Cargo.toml
codex-rs/mcp-server/tests/common/Cargo.toml
```
## What
- Remove `codex-rs/core/src/lib.rs` re-exports for symbols owned by
`codex-login`, `codex-mcp`, `codex-rollout`, `codex-analytics`,
`codex-protocol`, `codex-shell-command`, `codex-sandboxing`,
`codex-tools`, and `codex-utils-path`.
- Delete the `default_client` forwarding shim in `codex-rs/core`.
- Update in-crate and downstream callsites to import directly from the
owning `codex-*` crate.
- Add direct Cargo dependencies where callsites now target the owner
crate, and remove `codex-core` from `codex-rs/backend-client`.
## Why
`argument-comment-lint` was green in CI even though the repo still had
many uncommented literal arguments. The main gap was target coverage:
the repo wrapper did not force Cargo to inspect test-only call sites, so
examples like the `latest_session_lookup_params(true, ...)` tests in
`codex-rs/tui_app_server/src/lib.rs` never entered the blocking CI path.
This change cleans up the existing backlog, makes the default repo lint
path cover all Cargo targets, and starts rolling that stricter CI
enforcement out on the platform where it is currently validated.
## What changed
- mechanically fixed existing `argument-comment-lint` violations across
the `codex-rs` workspace, including tests, examples, and benches
- updated `tools/argument-comment-lint/run-prebuilt-linter.sh` and
`tools/argument-comment-lint/run.sh` so non-`--fix` runs default to
`--all-targets` unless the caller explicitly narrows the target set
- fixed both wrappers so forwarded cargo arguments after `--` are
preserved with a single separator
- documented the new default behavior in
`tools/argument-comment-lint/README.md`
- updated `rust-ci` so the macOS lint lane keeps the plain wrapper
invocation and therefore enforces `--all-targets`, while Linux and
Windows temporarily pass `-- --lib --bins`
That temporary CI split keeps the stricter all-targets check where it is
already cleaned up, while leaving room to finish the remaining Linux-
and Windows-specific target-gated cleanup before enabling
`--all-targets` on those runners. The Linux and Windows failures on the
intermediate revision were caused by the wrapper forwarding bug, not by
additional lint findings in those lanes.
## Validation
- `bash -n tools/argument-comment-lint/run.sh`
- `bash -n tools/argument-comment-lint/run-prebuilt-linter.sh`
- shell-level wrapper forwarding check for `-- --lib --bins`
- shell-level wrapper forwarding check for `-- --tests`
- `just argument-comment-lint`
- `cargo test` in `tools/argument-comment-lint`
- `cargo test -p codex-terminal-detection`
## Follow-up
- Clean up remaining Linux-only target-gated callsites, then switch the
Linux lint lane back to the plain wrapper invocation.
- Clean up remaining Windows-only target-gated callsites, then switch
the Windows lint lane back to the plain wrapper invocation.
### Overview
This PR adds the first piece of tracing for app-server JSON-RPC
requests.
There are two main changes:
- JSON-RPC requests can now take an optional W3C trace context at the
top level via a `trace` field (`traceparent` / `tracestate`).
- app-server now creates a dedicated request span for every inbound
JSON-RPC request in `MessageProcessor`, and uses the request-level trace
context as the parent when present.
For compatibility with existing flows, app-server still falls back to
the TRACEPARENT env var when there is no request-level traceparent.
This PR is intentionally scoped to the app-server boundary. In a
followup, we'll actually propagate trace context through the async
handoff into core execution spans like run_turn, which will make
app-server traces much more useful.
### Spans
A few details on the app-server span shape:
- each inbound request gets its own server span
- span/resource names are based on the JSON-RPC method (`initialize`,
`thread/start`, `turn/start`, etc.)
- spans record transport (stdio vs websocket), request id, connection
id, and client name/version when available
- `initialize` stores client metadata in session state so later requests
on the same connection can reuse it
Reapply "Add app-server transport layer with websocket support" with
additional fixes from https://github.com/openai/codex/pull/11313/changes
to avoid deadlocking.
This reverts commit 47356ff83c.
## Summary
To avoid deadlocking when queues are full, we maintain separate tokio
tasks dedicated to incoming vs outgoing event handling
- split the app-server main loop into two tasks in
`run_main_with_transport`
- inbound handling (`transport_event_rx`)
- outbound handling (`outgoing_rx` + `thread_created_rx`)
- separate incoming and outgoing websocket tasks
## Validation
Integration tests, testing thoroughly e2e in codex app w/ >10 concurrent
requests
<img width="1365" height="979" alt="Screenshot 2026-02-10 at 2 54 22 PM"
src="https://github.com/user-attachments/assets/47ca2c13-f322-4e5c-bedd-25859cbdc45f"
/>
---------
Co-authored-by: jif-oai <jif@openai.com>
Add a new `codex app-server --analytics-default-enabled` CLI flag that
controls whether analytics are enabled by default.
Analytics are disabled by default for app-server. Users have to
explicitly opt in
via the `analytics` section in the config.toml file.
However, for first-party use cases like the VSCode IDE extension, we
default analytics
to be enabled by default by setting this flag. Users can still opt out
by setting this
in their config.toml:
```toml
[analytics]
enabled = false
```
See https://developers.openai.com/codex/config-advanced/#metrics for
more details.