## Why
Managed network configures commands to use local HTTP and SOCKS proxies.
For commands delegated to the exec server, the proxy environment and the
sandbox policy were prepared separately. On macOS, that meant a command
could receive `HTTPS_PROXY=http://127.0.0.1:43123` while Seatbelt still
denied access to port `43123`.
## What changed
`NetworkProxy` now prepares the command environment and sandbox context
together from the same runtime snapshot:
```text
Prepared managed network
├── command environment: HTTPS_PROXY=http://127.0.0.1:43123
└── sandbox context: allow outbound to 127.0.0.1:43123
```
That context travels with remote exec requests. The exec server
preserves the managed proxy and CA environment, and macOS Seatbelt
allows only the prepared loopback proxy ports without enabling broad
network access or local binding.
The protocol field is optional and the existing enforcement flag remains
in place, preserving compatibility with callers that do not send the new
context.
## Why
The rollout persistence metrics added on current `main` exhaustively
match `ResponseItem`, but omit `ResponseItem::AdditionalTools`. That
prevents `codex-rollout` and downstream targets from compiling across
Cargo and Bazel builds.
## What
Map `ResponseItem::AdditionalTools` to the `response.additional_tools`
metric label, consistent with the existing exact-variant labels.
## Validation
- `just test -p codex-rollout` (76 passed)
- `just fix -p codex-rollout`
## Summary
Handle `ResponseItem::AdditionalTools` in rollout persistence metrics.
The persistence metrics match was added after the `AdditionalTools`
variant and omitted it, causing release builds to fail with a
non-exhaustive pattern error. This assigns the item the
`response.additional_tools` metrics label.
Release failure:
https://github.com/openai/codex/actions/runs/28043786727/job/83016608475
## Validation
- `just fmt`
- `just test -p codex-rollout` (76 passed)
## Why
Follow-up to #29249 and its [compaction review
thread](https://github.com/openai/codex/pull/29249#discussion_r3455055101).
During a turn, environment readiness can change between sampling
requests. Inline compaction must render the same model-visible
`WorldState` used by the request it follows. Rebuilding that state
during compaction can observe a newer environment, make replacement
history disagree with what the model saw, and suppress the next
environment update.
## What changed
- Make `run_turn` own the current `Arc<WorldState>` and replace it only
between sampling requests.
- Build each state from an explicitly chosen environment snapshot, diff
deferred-executor steps against the turn-owned state, and retain the
latest state in `ContextManager` only for cross-turn and resume
tracking.
- Pass the exact turn-owned state into inline compaction and explicit
new-context-window replacement.
- Carry that state with
`InitialContextInjection::BeforeLastUserMessage`, so replacement context
and its stored baseline cannot come from different snapshots.
- Remove obsolete state-recapture helpers and ambiguous TurnContext-only
WorldState builders.
- Add an integration test that moves an environment from starting to
ready during a paused turn, triggers compaction, and verifies the next
request receives the readiness update exactly once.
## Test plan
- `just test -p codex-core
deferred_executor_compaction_preserves_then_updates_environment_once`
- `just test -p codex-core process_compacted_history`
- `just test -p codex-core mid_turn_continuation_compaction`
- `just test -p codex-core build_initial_context`
- `just test -p codex-core
ignores_session_prefix_messages_when_truncating`
## Summary
MCP refresh replaced the published connection manager without shutting
down the manager it superseded. If another task retained that old
manager, its stdio MCP processes stayed alive and accumulated across
refreshes.
Atomically swap in the refreshed manager, then explicitly shut down the
exact manager returned by the swap. Add a process-level regression test
that retains the old manager during refresh and verifies its stdio
process exits while the replacement remains available.
## Context
Explicit cleanup was lost when manager publication moved to `ArcSwap`.
Dropping the old manager is not a reliable shutdown boundary because
active callers can retain its `Arc` and underlying client process
handles.
## Summary
- rename `reminder_interval_model_requests` to
`reminder_interval_seconds`
- read the configured time provider before every model request and
inject a reminder only after the configured number of seconds has
elapsed
- preserve immediate first delivery and forced delivery after compaction
changes the context window
## Tests
- `just test -p codex-core current_time_reminder`
- Add 1%-sampled rollout persistence metrics that report per-item and
per-thread JSON byte totals before and after filtering when metrics
export is enabled.
- Tag each item with its exact response or event variant, including
nested turn-item kinds for conditionally persisted completion events, so
aggregate cloud-storage impact can be estimated by policy choice.
## Summary
- Pin `hono` to 4.12.25, the first patched release for the recent Hono
security advisories.
- Pin `fast-uri` to 3.1.1 to fix the percent-encoded path traversal
vulnerability.
- Refresh `pnpm-lock.yaml` with only those dependency updates.
`hono` 4.12.25 is used instead of the newer 4.12.27 because the
repository requires dependencies to be at least seven days old.
## Summary
- Update `rmcp` and `rmcp-macros` from 1.7.0 to 1.8.0.
- Adapt to the new shared `peer_info` return type.
- Box OAuth status discovery at the MCP boundary to keep the expanded
future type from overflowing Rust's trait recursion limit.
This brings in custom OAuth HTTP client support from
[modelcontextprotocol/rust-sdk#908](https://github.com/modelcontextprotocol/rust-sdk/pull/908).
## Summary
Resuming a persisted thread currently deep-clones its complete rollout
history several times. `InitialHistory` is retained for the app-server
response, copied into thread persistence, and copied again by read-only
accessors. These copies scale with the complete rollout rather than the
bounded model context and add measurable latency for large sessions.
This change stores resumed rollout history in `Arc<Vec<RolloutItem>>`.
Rollout loading wraps the parsed vector once, while app-server response
construction, session initialization, and thread persistence share it
through inexpensive `Arc` clones. Read-only history access now returns a
borrowed slice, and fork paths use `Arc::unwrap_or_clone` where they
genuinely need mutable ownership. Rollout reconstruction also consumes
its temporary context instead of cloning the reconstructed model
history.
The serialized representation remains unchanged. In an artificial 123 MB
rollout benchmark, sharing resumed history reduced cold resume latency
by roughly 9–10%. The affected crates compile with their test targets,
all 80 thread-store tests pass, and the Bazel dependency lock remains
valid.
## Why
Executor-owned paths must stay portable while the orchestrator reasons
about them. Converting a Windows or remote path to the orchestrator
host's native path just to check containment breaks that boundary.
## What changed
- Add lexical containment to `PathUri`.
- Compare URI authorities and complete path segments, so `plugin-other`
is not treated as a child of `plugin`.
- Fail closed for encoded path separators and opaque fallback URIs.
For example:
```text
file:///C:/plugins/foo/assets/icon.svg
is below file:///C:/plugins/foo
file:///C:/plugins/foo2/icon.svg
is not below file:///C:/plugins/foo
```
This is the shared foundation for keeping executor-owned plugin
resources URI-native without consulting the orchestrator filesystem.
## Summary
Multi-agent v2 tools now use the fixed `collaboration` namespace when
namespace tools are available. This keeps the model-visible hint and the
actual tool surface aligned around `functions.collaboration.*`, without
exposing an unshipped namespace knob to users.
The PR also removes the old `features.multi_agent_v2.tool_namespace`
config/schema surface, updates the MAv2 test fixtures for namespaced
calls, and fixes stale `TurnContext.features` references that were
breaking `codex-core` builds.
## Changes
- Expose MAv2 tools under `collaboration` instead of relying on a
configurable namespace.
- Remove `tool_namespace` from MAv2 TOML config, resolved config,
validation, schema, and tests.
- Update tool-planning and integration fixtures to assert or emit
namespaced MAv2 tool calls.
- Read feature state through `TurnContext.config.features` in the
multi-agent mode context paths.
## Testing
- `just write-config-schema`
- `just test -p codex-features`
## Summary
- Require the reserved Codex Apps MCP server name to be present in the
connection manager before treating it as host-owned.
- Update auth elicitation tests to model an installed host-owned Codex
Apps server without sending startup events to the test session.
## Why
PR #29518 replaced the old host-owned flag with a name-only check. That
made non-host-owned tests with the reserved codex_apps name enter auth
elicitation and wait forever for a response.
## Why
The 0.142.0 persistent-log filter disables target=log, but bridged log
records are filtered using their original dependency target before
tracing-log emits them as target=log. This allowed high-volume
dependency TRACE events to keep reaching SQLite.
This is a follow-up to #28224.
## What changed
- Reject bridged target=log events inside the SQLite sink before
formatting or queueing them.
## Summary
- let `codex sandbox` accept the JSON value from
`codex/sandbox-state-meta`
- require the payload `permissionProfile` instead of falling back to
ambient permissions
- reuse the existing macOS, Linux, and Windows launch paths, treating
external sandbox state conservatively as read-only
- let opaque forwarders add runtime read roots and disable direct
network access without decoding the payload
Builds on #29113, which is now on `main`.
## Tests
- `just test -p codex-cli debug_sandbox::tests`
- `cargo build -p codex-rmcp-client --bin test_stdio_server`
- `just test -p codex-core
stdio_mcp_tool_call_includes_sandbox_state_meta`
- `just test -p codex-mcp`
- `just fmt`
## Why
`McpConnectionManager::new` classified the Codex Apps server twice: once
to create its tools cache context and again to select its runtime
authentication provider. Keeping those decisions separate makes it
harder to see that they belong to the same server-specific setup path.
## What changed
- Group Codex Apps cache and authentication setup under one explicit
branch.
- Keep regular MCP server setup in the corresponding `else` branch.
- Limit environment bearer-token inspection to the Codex Apps path where
it affects runtime authentication.
## Why
Codex Apps cache writes are already restricted to Codex Apps call paths:
startup invokes the helper only from the Codex Apps branch, and hard
refresh operates on the reserved Codex Apps server directly. Rechecking
the server name inside the cache helper duplicates that classification
and leaves the helper with an argument that cannot change valid
behavior.
## What changed
- Remove the redundant server-name check and parameter from the cache
writer.
- Rename the helper to `write_codex_apps_tools_cache` to reflect its
narrower contract.
- Update production and test callsites to use the simplified API.
## Why
`ResponseItem::AdditionalTools` was added without updating app-server
image URL validation. The exhaustive match therefore prevents app-server
and downstream targets from compiling on `main`.
## What changed
Treat `AdditionalTools` like the other response items that cannot
contain input-image URLs.
## Why
Codex Apps-specific behavior is currently distributed across cache
helpers, startup, tool conversion, and model-visible annotation. Each
layer independently checks the reserved server name, which obscures the
boundary between trusted host-owned connector metadata and regular MCP
server data.
Classifying the server once when `AsyncManagedClient` is created gives
the client a single source of truth and makes the two processing paths
explicit.
## What changed
- Record whether an `AsyncManagedClient` represents the Codex Apps
server at construction time.
- Route startup cache loading, cache persistence, and cache telemetry
through the Codex Apps branch.
- Split uncached tool conversion between Codex Apps normalization and
regular MCP metadata sanitization.
- Split model-visible schema and plugin provenance handling along the
same boundary.
- Remove redundant server-name guards from helpers that are now called
only from the Codex Apps branch.
## Verification
- Preserve behavioral coverage that verifies Codex Apps connector
metadata and the complete converted `ToolInfo` shape.
## Stack
Depends on #29518.
When using Responses Lite, we should all use `additional_tools` and a
developer item instead of the top level tools array & instructions
field. This keeps things 1-to-1.
Forced namespacing for _all_ tools will land in a following PR after
some coordination & fixes in Responses API (around collisions & return
items).
The goal is to eventually expand the scope of this to _all_ requests
from codex, but that will require larger coordination across providers &
slower rollout.
## Why
Codex Apps server admission is already decided before
`McpConnectionManager` is constructed. `effective_mcp_servers` and
`effective_mcp_servers_from_configured` remove the server when the apps
feature or required authentication is unavailable, so storing the same
decision on the manager duplicates state that can drift from the
effective server map.
## What changed
- Remove `host_owned_codex_apps_enabled` from `McpConnectionManager` and
its constructor.
- Identify the host-owned Codex Apps server by its reserved server name
once it is present in the effective server map.
- Remove the now-unused flag calculations and constructor arguments from
production and test callsites.
## Summary
- express remote compaction result handling as an exhaustive match
- preserve the special `TurnAborted` path without emitting a generic
compaction error
- rely on the standard `test_codex` provider setup in the compaction
budget test
Follow-up to review feedback on #28707.
## Testing
- `just test -p codex-core
compaction_budget_exhaustion_aborts_without_error_or_retry`
- `just fmt`
## Summary
NVIDIA asked to measure Fast mode usage and reasoning effort from Codex
CLI OTEL logs. Add the finalized `service_tier` and
`model_reasoning_effort` to the existing `codex.sse_event`
`response.completed` record.
This intentionally reuses the existing completion event and leaves
transport APIs and shared telemetry plumbing unchanged.
## Testing
- `cargo build -p codex-cli --bin codex`
- `just test -p codex-core responses_api_emits_api_request_event`
- End-to-end with the built CLI and a local OTLP/HTTP collector:
- Fast/high emitted `service_tier=priority` and
`model_reasoning_effort=high` with token usage.
- Standard/low omitted `service_tier` and emitted
`model_reasoning_effort=low` with token usage.
## Why
On Windows, Codex uses a PowerShell safe-command classifier to decide
whether a command is read-only enough to run without additional
approval. The classifier lowers `EndBlock.Statements` into argv-like
command words and checks those words against a safelist.
PowerShell can execute code stored elsewhere in the AST. Parameter
defaults, named blocks, `using` preambles, and top-level `trap` handlers
are not represented in the lowered statement list. Ignoring those
regions can make a side-effecting script look like a read-only command.
## What
Fail closed whenever a PowerShell script contains executable AST content
that the current lowering does not represent.
## How
- Return `unsupported` for parameter, dynamic-parameter, begin, process,
and clean blocks.
- Return `unsupported` for `using module` and `using assembly`
preambles.
- Return `unsupported` for non-empty `EndBlock.Traps` collections.
- Preserve compatibility with Windows PowerShell 5.1 by looking up
`CleanBlock` dynamically.
- Treat `unsupported` as a failure to prove that the command is safe,
routing it through the normal approval path.
- Add parser-level and end-to-end regressions for parameter blocks,
named blocks, using statements, and trap handlers.
This does not make these PowerShell forms invalid or prevent them from
running. It prevents automatic safe-command approval when the classifier
cannot account for all executable behavior.
## Testing
- `just test -p codex-shell-command`
- Windows CI exercises the parser and end-to-end safe-command
regressions against a real PowerShell installation.
---------
Co-authored-by: viyatb-oai <viyatb@openai.com>
## Summary
- read the request-scoped safety-buffering treatment from HTTP response
headers and per-turn WebSocket metadata through one shared header parser
- combine that treatment with Responses API safety-buffering signals
- propagate `showBufferingUi` and nullable `fasterModel` through the
existing `model/safetyBuffering/updated` app-server notification
- update the app-server documentation and generated JSON and TypeScript
schemas
The public implementation contains no model mapping or real model
identifier. Tests and protocol examples use generic `current-model` and
`faster-model` placeholders only.
## Dependencies
- server-side treatment evaluation:
https://github.com/openai/openai/pull/1060247
- initial Responses API safety-buffering propagation:
https://github.com/openai/codex/pull/29371
- Codex App UI: https://github.com/openai/openai/pull/1057789
## Validation
- Codex API tests: 129 passed
- focused Codex core safety-buffering integration test passed
- app-server protocol tests passed after regenerating schema fixtures
- Clippy fix and repository formatting completed successfully
The broader app-server run compiled all changed crates and completed
with 1,269 passing tests. Its remaining failures were unrelated
environment limitations: macOS sandbox application was denied, one
expected test binary was unavailable, and several existing subprocess
tests timed out as a result.
## Why
Remote stdio MCP servers can run in an environment whose path convention
differs from the Codex host. A Windows cwd such as
`C:\Users\openai\share` is absolute for the executor but was rejected by
a POSIX orchestrator.
Built on #29501, now merged, which only clarifies the host-native
`PathUri` constructor name.
## What changed
- Deserialize MCP cwd values as `LegacyAppPathString` so config does not
apply host path rules.
- Interpret that spelling as host-native for local launches and convert
it to `PathUri` at executor launch.
- Skip host filesystem and command resolution checks for remote stdio in
`codex doctor`.
- Add host-independent config and executor-boundary coverage using the
foreign path convention for each test platform.
## Validation
- `just test -p codex-utils-path-uri -p codex-config -p codex-mcp -p
codex-rmcp-client` (408 passed)
- `just test -p codex-cli -p codex-rmcp-client` (372 passed)
- `cargo check --workspace --tests`
- `just test` (11,311 passed; 43 unrelated environment/timing failures)
- `just fix -p codex-cli -p codex-config -p codex-core -p codex-mcp -p
codex-mcp-extension -p codex-rmcp-client -p codex-tui`
## Summary
Stacked on #26708.
Adds the macOS implementation of the shared system-proxy contract. This
allows Codex-owned auth clients to use the route macOS selects for each
auth URL through SystemConfiguration and CFNetwork, including PAC and
WPAD results.
The `respect_system_proxy` feature is disabled by default, so existing
client behavior remains unchanged unless explicitly enabled.
## Implementation
- Adds the macOS-only `system-configuration` dependency to
`codex-client`.
- Dispatches system-proxy resolution to `outbound_proxy/macos.rs` on
macOS.
- Reads system proxy settings from `SCDynamicStore` and resolves the
target URL with `CFNetworkCopyProxiesForURL`.
- Executes PAC URLs and inline PAC JavaScript through a bounded run loop
with a five-second timeout.
- Handles `DIRECT`, HTTP proxies, and CFNetwork HTTPS entries using HTTP
CONNECT; unsupported SOCKS entries map to `UnsupportedProxyScheme`.
- Builds concrete proxy URLs from host and port entries, including IPv6
host bracketing.
- Maps results into the shared `SystemProxyDecision::{Direct, Proxy,
Unavailable}` contract.
- Hashes URL-specific cache keys so PAC decisions remain distinct
without retaining raw request URLs or query strings.
## End-user behavior
- Disabled/default: existing client behavior is unchanged.
- Enabled with `[features.respect_system_proxy]`:
- macOS auth clients honor system proxy configuration, PAC, and WPAD;
- valid OS/PAC `DIRECT` decisions use a direct connection;
- unavailable system resolution falls back to explicit environment proxy
variables, then `DIRECT`, through the shared contract from #26707.
- Unsupported proxy schemes are not silently translated into another
route.
- Custom CA handling remains separate from proxy selection.
- Known limitation: only the first supported system/PAC candidate is
used. Subsequent proxy or `DIRECT` candidates are not attempted after a
connection failure. This matches the current Windows behavior and leaves
room for future ordered-fallback support.
## Tests
- `just test -p codex-client` — 34 tests passed.
- `just clippy -p codex-client`
- `just fmt`
- `just bazel-lock-check`
## Why
`just fmt` is quite noisy even on successful runs.
## What
Only print output when a formatter fails.
- Buffer output from each formatter and print only a failed command and
its diagnostics.
- Prefix the `justfile` driver invocations with `@` so Just does not
echo the command itself.
- Retain rustfmt stderr on failure and cover silent-success and
failure-reporting behavior.
## Validation
- Confirmed `just fmt` and `just fmt-check` both exit successfully with
empty stdout and stderr.
## Why
Amazon Bedrock returns a `401 Unauthorized` response containing
`Signature expired:` when an AWS credential, including a short-lived
`AWS_BEARER_TOKEN_BEDROCK`, has expired. Codex currently surfaces that
response as a generic `unexpected status` error, which does not explain
how to recover.
Environment-provided bearer tokens cannot be refreshed automatically, so
the error should direct users to refresh their AWS credentials or
replace or remove the environment token and restart Codex. This
classification belongs to the Amazon Bedrock provider so similar
responses from other providers retain their existing behavior.
## What changed
- Add a synchronous `ModelProvider::map_api_error` hook that defaults to
the existing provider-neutral API error mapping, and route model
request, stream, WebSocket, and terminal unauthorized errors through the
active provider.
- Override the hook for Amazon Bedrock. After preserving the structured
status, body, URL, and request metadata, recognize `401` responses
containing `Signature expired:` and attach actionable credential
guidance.
- Keep `codex-protocol` provider-neutral by representing the guidance as
an optional `user_message`. Error rendering prefers this message while
continuing to append the URL, request ID, Cloudflare ray, and
authorization diagnostics.
- Add model-provider coverage for expired signatures and negative cases,
core coverage for provider dispatch after unauthorized recovery, and a
TUI snapshot for the rendered error.
## Testing
Tested with a real request with expired bedrock key:
<img width="962" height="126" alt="Screenshot 2026-06-22 at 3 56 51 PM"
src="https://github.com/user-attachments/assets/7e21cc7c-798e-4662-8467-7f304a2f2b59"
/>
## Stack
Stacked on #29417. Review and land that PR first.
## Summary
- reject HTTP(S) image URLs in the handlers for `turn/start` and
`turn/steer`
- validate `thread/inject_items` after its existing
JSON-to-`ResponseItem` conversion, so each item is deserialized once
- turn invalid dynamic-tool image responses into the existing
unsuccessful text fallback; the model receives the validation message as
the function output
- leave `thread/resume.history` compatible with legacy history; #29417
replaces remote images before model input
- continue accepting inline data URLs and `localImage` inputs
- keep this policy in app-server; this PR does not add a shared protocol
API or change core image preparation
## Test plan
- `just test -p codex-app-server -E
'test(/request_handlers_reject_remote_image_urls|dynamic_tool_remote_image_response_becomes_model_visible_error|dynamic_tool_call_round_trip_sends_content_items_to_model|turn_start_tracks_turn_event_analytics|standalone_image_edit_uses_recent_pathless_image/)'`
(5 passed)
- `just fix -p codex-app-server`
- `just fmt`
Remote plugin catalogs now span workspace, shared, and local sources, so
their TUI behavior needs focused regression coverage across loading,
navigation, actions, and refreshes.
This PR:
- Covers product labels and rendered loading/error states for Workspace,
Shared with me, Shared with me (link), and Local tabs, including tab
persistence across refresh and detail navigation.
- Covers remote/local deduplication, Installed-tab remote detail
routing, marketplace load-error handling, and disabled install/uninstall
navigation.
- Adds a full detail snapshot for local shared-plugin metadata plus
focused snapshots for marketplace labels and admin-disabled status.
- Verifies shared plugins remain eligible for mentions and successful
uninstalls trigger a catalog refresh.
seems to be a merge conflict on main:
> pakrym-oai introduced the stale initializer in commit 3b32d861c5, PR
#29249.
> Context: Owen Lin renamed metadata to
internal_chat_message_metadata_passthrough in PR #28968. PR #29249 then
landed afterward with the old field name, causing the compile/Clippy
failure.
## Why
Downstream refactors are producing confusing code with this
functionality having a very generic name. Encoding the specific
conversion approach in the method name makes it clearer.
## What
Rename `PathUri::from_path` to `PathUri::from_host_native_path` and
update its Rust call sites.
## Why
MCP tools were only placed behind `tool_search` when a feature flag was
enabled or when there were at least 100 tools. That made the model's
tool flow depend on both rollout configuration and the number of
installed tools.
The searched-tool flow is now the intended behavior. Making it
unconditional when the model and provider support it gives every
supported setup the same behavior and lets us retire the feature flag
safely.
## What changed
- Defer all effective MCP tools when `tool_search` and namespaced tools
are supported.
- Keep exposing MCP tools directly when search cannot be used, so older
or unsupported model/provider combinations still work.
- Mark `tool_search_always_defer_mcp_tools` as removed and ignore old
configured values.
- Keep plugin filtering, app-only filtering, file handling, and MCP
calls working through the searched-tool flow.
## Why many tests changed
Many tests used to act as if the model could see MCP tools in its first
request and call them immediately. That is no longer the real flow: the
model first receives `tool_search`, searches for a tool, receives the
matching MCP tool, and then calls it in the next request.
The tests therefore needed an extra search step, and checks for tool
names, descriptions, and input fields had to move from the first request
to the search result. These are not separate product changes; they make
the tests follow what the model will actually see after this change. The
plugin tests still check which tools are allowed and where they came
from, the file tests still check upload fields and behavior, and the MCP
round-trip test still checks a successful call from start to finish.
## Tests
- `just test -p codex-features`
- Focused `codex-core` tests for MCP exposure and tool planning
- `just test -p codex-core explicit_plugin_mentions`
- `just test -p codex-core stdio_server_round_trip`
- Focused `codex-core` tests for tool search, app-only tools, and MCP
file uploads
## Description
This PR is a followup to https://github.com/openai/codex/pull/28355 and
starts assigning `internal_chat_message_metadata_passthrough.turn_id` to
durable Responses API items created during a turn.
The goal is that those items keep the `turn_id` that introduced them
when Codex resends stateless HTTP context, reconstructs history for
resume/fork paths, or reuses websocket response state.
## What changed
- Set `internal_chat_message_metadata_passthrough.turn_id` when missing
as response items enter durable history, initial/replacement history,
inter-agent communication history, and local compaction summaries.
- Preserve existing item turn IDs instead of overwriting them during
persistence, resume reconstruction, compaction, forked history, and
websocket incremental reuse.
- Keep `compaction_trigger` fieldless because it is a request control,
not a durable response item.
- Update focused history/request assertions and fixtures for stateless
requests, websocket incrementals, compaction, thread injection, prompt
debug, and related CI coverage.
## What
This PR will extend the existing centralized image-preparation path to
replace HTTP(S) image inputs with a model visible error message. It
won't "ruin" and break existing rollouts, but it will deprecate support
for the pathway. App server clients should no longer use HTTP image urls
if they'd like to upgrade.
The HTTP image url pathway is currently resolved in the responsesapi. It
is slow and not reccomended.
## Behavior
- HTTP(S) image URL: replace with `input_text`
- data URL: use the existing decode and resize path
- other image URL schemes: leave unchanged
This intentionally does not change app-server ingress. That validation
remains a follow-up.
## Test plan
- `just test -p codex-core -E
'test(/image_preparation|prepares_image_failures_before_history_insertion|prepares_resumed_history_before_installing_it|responses_lite_prepares_images/)'`
— 7 passed
- `just fix -p codex-core`
- `just fmt`
Token-budget initial context carries thread and context-window lineage
that the model should treat as one structured context-window block.
Wrapping it in `<context_window>` makes that boundary explicit while
preserving the existing window id content.
Before this change, the window identifiers were injected as an untagged
developer text fragment:
```text
Thread id <THREAD_ID>.
First context window id: <FIRST_WINDOW_ID>
Current context window id: <WINDOW_ID>
Previous context window id: <PREVIOUS_WINDOW_ID>
```
After this change, the same payload is wrapped as a context-window
block:
```text
<context_window>
Thread id: <THREAD_ID>
First context window id: <FIRST_WINDOW_ID>
Current context window id: <WINDOW_ID>
Previous context window id: <PREVIOUS_WINDOW_ID>
</context_window>
```
This adds shared `CONTEXT_WINDOW_*_TAG` protocol constants, updates
`TokenBudgetContext` to render with those markers, treats the new
wrapper as contextual developer content when mapping history, and
refreshes the token-budget request-shape assertions and snapshot.
Verification:
- `just test -p codex-core token_budget`
- `just test -p codex-core
recognizes_context_window_as_contextual_developer_content`
## Why
Environment context is model-visible state, but it is currently
assembled from transient turn values and diffed through
environment-specific paths. That makes initial injection, turn-to-turn
updates, and changes that happen within a turn use different baselines.
This PR introduces the smallest useful model world-state slice:
environments only, with one in-memory baseline and one renderer for full
state and diffs.
## What changed
- Add a typed `WorldState` container whose sections render fragments
relative to an optional previous value. Full rendering uses the same
diff path with no previous state.
- Replace the parallel `EnvironmentContext` representation with an
`EnvironmentsState` section keyed by environment ID and rendered in
deterministic order.
- Preserve the legacy single-environment output while supporting
multiple environments, starting environments, unavailable tombstones,
and changes to persisted turn-context values.
- Store the latest complete `WorldState` on `ContextManager` and use it
for both turn-boundary and mid-turn environment diffs.
- Build initial and post-compaction context from the same world-state
builder, then retain the rendered state as the next baseline.
- Seed the in-memory baseline from the latest `TurnContextItem` when
resuming an existing rollout; the world state itself is not serialized.
- Keep non-world settings updates on their existing path and merge
rendered world-state fragments at the session consumer.
## Known limitation
A legacy `TurnContextItem` only reconstructs the primary environment as
`local`; it cannot faithfully recover a remote-primary environment ID
after resume. Live state uses the exact environment IDs once a complete
baseline is established.
## Test plan
- `just test -p codex-core world_state`
- `just test -p codex-core record_context_updates`
- `just test -p codex-core deferred_executor_`
- `just test -p codex-core build_initial_context`
- `just test -p codex-core rollout_reconstruction`
- `just test -p codex-core
process_compacted_history_reinjects_full_initial_context`
Adds additive dark-mode plugin logo metadata across manifests, remote
catalogs, and the app-server protocol while keeping uninstalled Git
listings free of synthetic local paths.
Supersedes #28945. This replacement uses an upstream branch so trusted
CI can use the repository-provided remote Bazel configuration.
## Current state
Plugin interfaces expose only the default logo asset. Clients therefore
cannot select a dedicated dark-mode logo even when a plugin provides
one.
## What this PR changes
- Adds nullable `logoDark` and `logoUrlDark` fields to
`PluginInterface`.
- Resolves local `interface.logoDark` assets and maps remote
`logo_url_dark` values.
- Removes path-backed interface assets, including `logoDark`, from
uninstalled Git fallback listings until the plugin has a real local
root.
- Updates the bundled plugin validator and manifest reference.
- Regenerates the app-server JSON schemas and TypeScript types.
Local manifests expose `interface.logoDark` as a package-relative asset
path. Remote catalog responses expose `logo_url_dark`. These values map
into separate app-server fields so clients can preserve local-path and
remote-URL handling.
## Risk
The fields are additive and nullable, so existing clients retain their
current logo behavior. The main risks are an incomplete mapping path or
exposing a synthetic local path for an uninstalled Git plugin.
Local-manifest, remote-catalog, fallback-listing, protocol
serialization, and app-server integration tests cover those paths.
Spiciness: 2/5
## Testing
- `just write-app-server-schema`
- `just fmt`
- Regression test first failed with `logo_dark` resolved to
`/assets/logo-dark.png`, then passed after the fallback-listing fix.
- `just test -p codex-core-plugins` (267 tests passed)
- `just test -p codex-app-server 'suite::v2::plugin'` (114 tests passed)
- `just test -p codex-app-server-protocol -p codex-core-plugins -p
codex-plugin -p codex-skills` (517 tests passed before the follow-up)
- `just test -p codex-tui plugin` (47 tests passed)
- Validated a local plugin manifest containing `interface.logoDark` with
the bundled validator.
## Manual verification
Create a local plugin with both `interface.logo` and
`interface.logoDark`, then call `plugin/list` or `plugin/read`. Confirm
the response contains separate `logo` and `logoDark` paths. For a remote
catalog entry, confirm `logoUrlDark` is populated from `logo_url_dark`.
For an uninstalled Git marketplace entry, confirm path-backed interface
assets remain absent until installation.
Issue: N/A - coordinated maintainer change.
## Why
The TypeScript workspace resolved `esbuild` 0.25.10 transitively through
the SDK toolchain. `esbuild` 0.28.1 adds integrity verification to the
Deno binary download path addressed by
[GHSA-gv7w-rqvm-qjhr](https://github.com/evanw/esbuild/security/advisories/GHSA-gv7w-rqvm-qjhr),
preventing an attacker-controlled npm registry from supplying an
executable without a content check.
## What changed
- Add a root workspace resolution for `esbuild` 0.28.1.
- Regenerate `pnpm-lock.yaml` so `tsup`, `bundle-require`, and `ts-jest`
all resolve the patched version.
## Validation
- Frozen pnpm install, including the SDK's `tsup` build
- `pnpm --filter @openai/codex-sdk exec jest tests/exec.test.ts
--runInBand`
- Confirmed the installed dependency graph contains only `esbuild`
0.28.1
## Summary
- upgrade the bundled OpenSSL source from 3.5.5 to 3.6.3
- update the Bazel `openssl-sys` build dependency to use the upgraded
source crate
- refresh the Bazel module lockfile
## Why
OpenSSL 3.5.5 is within the affected ranges for security issues fixed in
later releases. The Rust `openssl-src` wrapper does not currently
publish OpenSSL 3.5.7, so this moves the vendored Linux musl build to
the available patched 3.6.3 release.
## Summary
- fetch featured plugin IDs when the loaded catalog includes
`openai-curated-remote`
- extend the existing remote marketplace regression test to cover the
featured IDs response
## Why
When the remote plugin catalog was enabled, app-server loaded
`openai-curated-remote` but skipped `/plugins/featured` because the
request processor only fetched featured IDs for the local
`openai-curated` marketplace. As a result, the desktop app could not
render the backend-curated remote featured set.
This keeps the existing local behavior and also returns the curated
ranking for remote plugins.
## Test plan
- `just fmt`
- `git diff --check`
- `just test -p codex-app-server
plugin_list_includes_remote_marketplaces_when_remote_plugin_enabled`
## Summary
Stacked on #26707.
Adds the Windows implementation of the shared system-proxy contract.
This allows Codex-owned auth clients to use the route Windows selects
for each auth URL, including explicit PAC configuration, WPAD
auto-detection, static proxies, and bypass rules.
The `respect_system_proxy` feature is disabled by default, so existing
client behavior remains unchanged unless explicitly enabled.
## Implementation
- Adds Windows-only `codex-client` dependencies:
- `windows-sys` with `Win32_Foundation` and `Win32_Networking_WinHttp`;
- `sha2` for redacted cache keys.
- Dispatches system-proxy resolution to `outbound_proxy/windows.rs` on
Windows.
- Reads the current-user WinHTTP/IE proxy configuration via
`WinHttpGetIEProxyConfigForCurrentUser`.
- Resolves explicit PAC URLs first, then OS-enabled WPAD auto-detection,
then static proxy and bypass settings.
- Uses `WinHttpGetProxyForUrl` for PAC/WPAD and maps results into the
shared `SystemProxyDecision::{Direct, Proxy, Unavailable}` contract.
- Parses `DIRECT`, `PROXY`, `HTTPS`, and keyed static proxy entries.
- Treats unsupported schemes such as SOCKS as unavailable so the shared
resolver can apply its environment-proxy fallback.
- Handles Windows bypass entries, including `<local>` and host, suffix,
wildcard, and port matching.
- Releases WinHTTP-owned strings with `GlobalFree` and closes sessions
with `WinHttpCloseHandle`.
- Hashes URL-specific cache keys with SHA-256 so PAC decisions remain
URL-specific without retaining raw request URLs or query strings.
## End-user behavior
- Disabled/default: existing client behavior is unchanged.
- Enabled with `[features.respect_system_proxy]`:
- Windows auth clients honor explicit PAC configuration, OS-enabled
WPAD, static proxies, and bypass rules;
- valid OS/PAC `DIRECT` decisions use a direct connection;
- unavailable system resolution falls back to explicit environment proxy
variables, then `DIRECT`, through the shared contract from #26707.
- Unsupported proxy schemes are not silently translated into a different
route.
- Custom CA handling remains separate from proxy selection.
## Tests
Adds coverage for:
- PAC-style proxy tokens such as `PROXY proxy.internal:8080` and `HTTPS
proxy.internal:8443`;
- static WinHTTP proxy entries keyed by target scheme;
- `DIRECT` and unsupported proxy-token behavior;
- Windows bypass matching, including `<local>`, wildcard, suffix, and
port-qualified entries;
- preserving URL-specific PAC cache decisions without retaining the raw
URL on Windows.
## This PR
Remote plugin analytics cannot rely only on the in-memory
installed-plugin snapshot because that snapshot is refreshed
asynchronously after startup. This PR persists the authoritative backend
identity alongside each cached remote plugin bundle so later consumers
can resolve it without a network request.
### Behavior
- Store Codex-owned remote installation metadata in an atomic
`.codex-remote-plugin-install.json` sidecar under the plugin cache root.
- Use a versioned, snake_case schema:
```json
{
"schema_version": 1,
"remote_plugin_id": "plugins~Plugin_..."
}
```
- Write the metadata during remote bundle installation.
- Backfill it when bundle sync finds an already-current cached bundle.
- Clear it when a generic/local install replaces the cache.
- Let existing uninstall and stale-cache removal delete it with the
plugin cache root.
- Reject unsupported schema versions rather than silently misreading
future formats.
This PR does not change analytics serialization or event behavior.
### Review surface
The implementation is limited to four `codex-core-plugins` files:
- `store.rs`: owns the versioned sidecar read/write/remove lifecycle.
- `remote_bundle.rs`: persists the backend ID after a remote bundle
install.
- `remote/remote_installed_plugin_sync.rs`: backfills metadata for an
already-current cached bundle.
- Tests cover the storage lifecycle and both remote write paths.
## Testing / Validation
### Automated
- `just test -p codex-core-plugins` (268 tests passed)
- `just fix -p codex-core-plugins` passes with one pre-existing
`large_enum_variant` warning in `manifest.rs`.
- Coverage verifies the exact filename and JSON schema, identity
replacement, local reinstall clearing, uninstall cleanup, remote bundle
installation, unsupported schema rejection, and installed-plugin sync
backfill.
### Live manual validation
Validated the production app-server RPC path with an isolated temporary
`CODEX_HOME` and the PR-built Codex binary. The app-server communicated
over stdio and did not bind a port.
Test plugin: `plugins~Plugin_b80dd84519148191a409cde181c9b3d6`
(`build-macos-apps@openai-curated-remote`).
1. Confirmed `plugin/read` initially reported the plugin uninstalled.
2. Installed it through `plugin/install` and confirmed version `0.1.4`
was cached.
3. Verified
`$CODEX_HOME/plugins/cache/openai-curated-remote/build-macos-apps/.codex-remote-plugin-install.json`
was created beside the `0.1.4/` bundle directory with mode `0600` and
the expected contents:
```json
{
"schema_version": 1,
"remote_plugin_id": "plugins~Plugin_b80dd84519148191a409cde181c9b3d6"
}
```
4. Deleted only the sidecar, restarted the app-server, and confirmed
installed-plugin startup sync recreated it with the same contents.
5. Uninstalled through `plugin/uninstall`, confirmed `plugin/read`
returned `installed: false`, and verified the local plugin cache root
was removed.
6. Restored the account's original uninstalled state and removed the
isolated home and copied credentials.
## Split Overview
```text
main
├── #27093 Debug analytics capture merged
│ └── #27099 Non-mutating plugin smoke merged
│ └── #27100 Remote install/uninstall smoke merged
└── #27102 Plugin telemetry metadata refactor merged
└── #27669 Persist remote plugin identity ← this PR
Next:
└── Final PR: add explicit local and remote IDs to plugin analytics
```
This PR is based directly on `main`; prerequisite
[#27102](https://github.com/openai/codex/pull/27102) has merged. The
original combined [#26281](https://github.com/openai/codex/pull/26281)
remains the aggregate reference until the final replacement PR is
published.
## Why
`openai-oss-forks/tokio-tungstenite` now includes the updated
`tungstenite` fork revision from
[openai-oss-forks/tokio-tungstenite#3](https://github.com/openai-oss-forks/tokio-tungstenite/pull/3).
Codex should consume the merged fork commit and resolve its direct and
transitive `tungstenite` dependencies to the same revision instead of
retaining the older pins.
## What Changed
- Advanced the `tokio-tungstenite` git pin to
`0e5b2d73aa18dd9f0a50ee9ff199d5aef7594186`.
- Advanced the `tungstenite` fork pin to
`4fffad30fe373adbdcffab9545e9e9bf4f2fc19f` and adjusted the patch source
so the transitive dependency resolves to that revision.
- Updated `Cargo.lock` and `MODULE.bazel.lock` to match the dependency
graph.