Skip to content

checkpoint: into wallentx/termux-target from release/0.143.0 @ c13a6baae40b - #265

Merged
unemployabot[bot] merged 285 commits into
wallentx/termux-targetfrom
checkpoint/wallentx_termux-target_from_release_0.143.0_c13a6baae40b
Jun 26, 2026
Merged

checkpoint: into wallentx/termux-target from release/0.143.0 @ c13a6baae40b#265
unemployabot[bot] merged 285 commits into
wallentx/termux-targetfrom
checkpoint/wallentx_termux-target_from_release_0.143.0_c13a6baae40b

Conversation

@unemployabot

@unemployabot unemployabot Bot commented Jun 26, 2026

Copy link
Copy Markdown

Termux release checkpoint

  • Source branch: release/0.143.0
  • Source hash: c13a6baae40b2bb54333c2c790c27a911a730874
  • Destination branch: wallentx/termux-target
  • Remaining first-parent commits on source: 0

This PR carries release-train conflict fixes and follow-up changes back into the reusable Termux patch branch.

Release-only workflow files and metadata under .github were restored to the destination branch versions before opening this PR.

martinauyeung-oai and others added 30 commits June 18, 2026 14:02
## Summary

- Add optional `appContext` to app-server MCP tool-call items with
trusted `connectorId`, `linkId`, and `mcpAppResourceUri` metadata.
- Preserve that context across tool-call events, persisted history,
reconnects, and thread resume.
- Keep the deprecated top-level `mcpAppResourceUri` temporarily for
client migration.

The consumer contract is `{ appContext: { connectorId, linkId,
mcpAppResourceUri }, tool }`.

## Validation

- Full GitHub Actions suite passes, including CLA, Bazel tests, clippy,
release builds, and argument-comment lint.

---------

Co-authored-by: martinauyeung-oai <280153141+martinauyeung-oai@users.noreply.github.com>
## Stack

This is PR 2 of the simplified HAI single-run-task stack:

- [openai#19047](openai#19047) Agent Identity
assertion and task-registration primitives, including the shared
run-task helper used by existing Agent Identity JWT auth.
- [openai#19049](openai#19049)
Disabled-by-default ChatGPT auth opt-in that provisions/reuses persisted
Agent Identity runtime auth and its single run task.
- [openai#19051](openai#19051) Run-scoped
provider auth that uses one backend-owned task id for first-party
inference and compaction requests.

[openai#19054](openai#19054) collapsed out of
the active stack because the simplified design no longer needs a
separate background/control-plane task helper.

## Summary

This PR adds the disabled-by-default path for normal ChatGPT-login Codex
sessions to obtain Agent Identity runtime auth through the Codex
backend. Existing Agent Identity JWT startup mode remains a separate
path and does not require the feature flag.

What changed:

- adds the experimental `use_agent_identity` feature flag and config
schema entry
- adds an explicit `AgentIdentityAuthPolicy` so call sites choose
`JwtOnly` or `ChatGptAuth` instead of passing a bare boolean
- stores standalone Agent Identity JWT credentials separately from
backend-registered Agent Identity records
- persists the registered Agent Identity record, private key, and single
run task id in `auth.json` so process restarts reuse the same identity
- derives the agent/task registration base URL from ChatGPT/Codex auth
config while keeping JWT JWKS lookup separate
- provisions and caches ChatGPT-derived Agent Identity runtime auth when
`use_agent_identity` is enabled
- reuses the shared run-task registration helper from PR1 rather than
adding a second task-registration path

This PR intentionally does not switch model inference over to
`AgentAssertion` auth. The provider-auth integration lands in the next
PR.

## Testing

- `just test -p codex-login`
Summary
- Stop treating Codex Apps MCP tools with
`_meta._codex_apps.synthetic_link: true` as evidence that a connector is
accessible in `app/list`.
- Preserve synthetic tools in the agent-facing MCP connector set so they
remain available for install/auth flows.
- Keep the app-list accessibility cache limited to connectors backed by
at least one non-synthetic tool.
- Add focused regression coverage for both sides of the boundary.

Validation
- `just fmt`
- `just test -p codex-core
synthetic_links_are_exposed_to_the_agent_but_not_accessible_in_app_list`
- `git diff --check`
- A crate-wide `just test -p codex-core` run completed with 2,699
passing and 51 unrelated local sandbox/state failures, primarily state
DB migration races (`UNIQUE constraint failed:
_sqlx_migrations.version`).
## Summary

- preserve the original HTTP status when a remote plugin bundle download
returns a non-success response
- retain at most 8 KiB of the error response body and annotate
truncation or body-read failures
- add regression coverage for an oversized error response

## Root cause

The non-success response path reused the normal size-limited body
reader. When an error response exceeded 8 KiB, that reader returned
`DownloadTooLarge` before the code constructed `DownloadStatus`, masking
the upstream HTTP status and response context.

## Impact

Remote plugin installation failures now retain the actionable upstream
HTTP status without allowing unbounded error bodies into logs.

## Validation

- `just test -p codex-app-server
plugin_install_preserves_status_when_remote_bundle_error_body_is_too_large`
- `just fmt`
- `git diff --check`
## Why

Make it possible to load AGENTS.md from remote exec-servers whose OS is
different than app-server.

## What

- keep `AGENTS.md` discovery and provenance as `PathUri`, with
root-aware parent and ancestor traversal
- expose lifecycle instruction sources as legacy app-server path strings
in events while retaining `PathUri` internally
- preserve and test mixed POSIX and Windows paths in model context and
TUI status output
- cover remote Windows loading end to end by seeding the Wine prefix
through host filesystem APIs
- fix bug in `PathUri`'s parent() implementation that would erase
Windows drive letters
## Summary

Support marketplace plugins whose source directory does not include a
discoverable plugin manifest. Metadata-rich `marketplace.json` entries
now act as fallback plugin manifests for listing, local detail reads,
install, and non-curated cache refresh.

The fallback preserves marketplace-entry plugin fields wholesale, then
adds the small Codex-facing compatibility bridge for presentation
metadata. A real source `plugin.json` always wins when present.

## Details

- Capture flattened marketplace-entry fields into
`MarketplacePluginManifestFallback`, preserving fields such as
`version`, `description`, `skills`, `mcpServers`, `apps`, `hooks`,
`agents`, `commands`, `strict`, `author`, and future manifest fields
without a per-field translation list.
- Bridge Claude-style top-level `displayName`, `author.name`,
`homepage`, and marketplace `category` into Codex's nested `interface`
fields only when the nested values are absent.
- Treat fallback metadata as installable only when the marketplace entry
contributes metadata beyond bare `name` and `source`; existing
missing-manifest behavior remains for metadata-free entries.
- Read local plugin details from the already parsed fallback manifest,
including fallback-declared app and MCP paths, instead of rereading only
an on-disk manifest.
- Pass fallback contents into `PluginStore`, which validates them and
injects `.codex-plugin/plugin.json` into Store's existing atomic copy.
Local marketplace source directories are never mutated, and the fallback
path no longer needs an additional staging directory.
- Keep Git source materialization unchanged; Git clones still use the
existing marketplace source staging area before Store installation.
## Why

`child_agents_md` is a disabled, under-development experiment that adds
a second model-visible explanation of hierarchical `AGENTS.md` behavior.
Keeping it leaves unused prompt, configuration, documentation, and test
surface.

## What changed

- remove the `ChildAgentsMd` feature and `child_agents_md` config schema
entry
- remove the hierarchical prompt asset, export, and instruction
injection
- remove feature-specific tests and documentation
- keep the generic unstable-feature warning coverage using
`apply_patch_streaming_events`

Normal project `AGENTS.md` discovery and composition are unchanged.

## Testing

- `just test -p codex-features`
- `just test -p codex-prompts`
- `just test -p codex-core agents_md`
- `just test -p codex-core unstable_features_warning`
## Why

No need to do path contortions when it's for our own logs.

## What

Follow up on a previous PR's nit and update the path-types skill for
future reference.
## Why

We need to avoid resolving shells on the app-server's host for remote
environments. We might make it possible to do fancier shell resolution
from remote envs but for now just require the model to produce a shell
that matches the environment's default.

This gets my e2e demo working for shell commands after openai#28854 moved
shell resolution to PathUri and caused remote envs to hit the fallback
shell when the shell wasn't available on the host.

## What

Remote `exec_command` calls now accept only the environment's reported
default shell name or exact path, and execute with that reported path.
Other explicit shells return a concise error. A Wine-backed integration
test covers explicit PowerShell execution in the Windows cwd.
## Summary

- Preserve raw plugin skill-root snapshots in the matching loaded-plugin
cache entry, keyed by the effective plugin root identity including
namespace.
- Pass those snapshots through `SkillsLoadInput` as an optional preload,
so session startup reuses plugin parsing while ordinary skill loads pass
`None`.
- Keep plugin skill loading cohesive: the existing loaders accept the
optional snapshots directly, and uncached or marketplace-detail paths do
not create a cache.

## Why

Plugin discovery already parses plugin skills to determine available
capabilities. Cold session startup then scanned and parsed the same
roots again while building the skills snapshot.

This solves the same duplicate-work problem as openai#28623 while keeping
ownership narrow: `PluginsManager` creates and owns
`PluginSkillSnapshots` only for its loaded-plugin cache entry;
`SkillsService` consumes an optional clone. Entry replacement or
clearing naturally drops the snapshots, with no separate generation,
capacity policy, or watcher coupling.

## Validation

- `cargo clippy -p codex-core-skills --all-targets -- -D warnings`
- `just test -p codex-core-plugins
skills_service_reuses_skills_parsed_during_plugin_load`
- `just test -p codex-core-skills
namespaces_plugin_skills_using_provided_namespace`
- `just fmt`
## Why

The token-budget context currently identifies a context window by its
thread-local sequence number. A UUIDv7 gives the model a stable opaque
identity that remains fixed for a window and rotates when compaction or
`new_context` starts the next one.

## What changed

- Preserve the existing monotonic value as `window_number` and add a
UUIDv7 `window_id` to `CompactedItem`.
- Generate and rotate the UUID with auto-compaction window state,
persist it alongside the number, and reconstruct it on resume and
rollback.
- Accept legacy compacted rollout records where the numeric `window_id`
represented the window number.
- Use the UUID only in token-budget context; existing request headers
and metadata continue using `thread_id:window_number`.

## Testing

- `just test -p codex-protocol compacted_item::tests`
- `just test -p codex-core token_budget`
…#28951)

Summary
- Refresh the installed remote-plugin snapshot and Codex Apps tools
after completing a remote JIT install.
- Gate `completed: true` on every expected `app_connector_id` appearing
after the uncached `tools/list` refresh, while continuing to skip local
bundle verification for server-side installs.
- Keep the cached recommendations response and filter refreshed
installed remote IDs locally, so this does not add another
recommendations fetch.
- Add regression coverage for tools appearing after the hard refresh and
remaining absent after the refresh. The resumed model request sees the
refreshed tool router when installation completes.

Root Cause
- Remote suggestions from `openai-curated-remote` returned `true` before
taking the existing connector refresh path, leaving the resumed turn
with the pre-install Apps tool catalog.

Validation
- `just test -p codex-core request_plugin_install`
- `just test -p codex-core-plugins
recommended_plugin_candidates_filter_installed_and_disabled_plugins`
- `just test -p codex-core-plugins`
- `just fix -p codex-core-plugins`
- `just fix -p codex-core`
- `just fmt`
- `just test -p codex-core` was not fully clean locally: 2,729 passed,
26 failed, and 16 skipped. The failures were dominated by local
Seatbelt/network/timing issues, including plugin-install timeouts under
full-suite contention; the focused plugin-install runs pass.
## Summary

- Remove the realtime `architecture` selector from core protocol,
app-server protocol, config parsing, generated schemas, and callers.
- Always create WebRTC realtime calls with the AVAS query params:
`intent=quicksilver&architecture=avas`.
- Keep direct websocket realtime behavior on the existing config/default
path, while WebRTC starts without an explicit version now default to
realtime v1 because AVAS requires v1.

## Notes

- WebRTC realtime now means AVAS. If a caller explicitly asks to start
WebRTC with realtime v2, Codex rejects that request because the AVAS
WebRTC path only supports realtime v1. Websocket realtime is separate
and can still use realtime v2.
- The old `[realtime] architecture = "realtimeapi" | "avas"` config knob
is removed. Local configs that still set it will need to delete that
line.
- Some app-server tests that were only trying to exercise realtime v2
protocol behavior now use websocket transport, because WebRTC is
intentionally locked to AVAS/v1. Separate WebRTC tests cover the AVAS
query params, v1 startup, SDP flow, and sideband join.

## Validation

- Merged fresh `origin/main` at `83e6a786a2`.
- `just fmt`
- `just write-config-schema`
- `just write-app-server-schema`
- `git diff --check`
- `just test -p codex-api -p codex-core -p codex-app-server-protocol -p
codex-app-server realtime` (176 passed)
- `just test -p codex-protocol -p codex-config` (413 passed)
## Why

Client-created response items enter history without IDs, so their
identity is lost across rollout persistence and resume. IDs should be
assigned once at the history-recording boundary, while IDs returned by
the server must remain unchanged.

The Responses API validates item IDs using type-specific prefixes.
Locally generated IDs therefore use the matching prefix plus a
hyphenated UUIDv7, keeping them valid while distinguishable from
server-generated IDs. Because this changes persisted history and
provider request shapes, the behavior is opt-in behind the
under-development `item_ids` feature. Compaction triggers remain request
controls whose API shape does not accept an ID.

## What changed

- Register the disabled-by-default `item_ids` feature and expose it in
`config.schema.json`.
- Make supported optional `ResponseItem` IDs serializable and expose
them in the generated app-server schemas.
- When `item_ids` is enabled, assign an ID during conversation-history
preparation if an item has no ID.
- Generate type-prefixed, hyphenated UUIDv7 IDs using the Responses API
item conventions.
- Preserve existing server IDs without rewriting them.
- Persist assigned IDs in rollouts and include them in subsequent
Responses requests.
- Remove the unsupported ID field from `CompactionTrigger` and document
why it has no ID.
- Add integration coverage for enabled ID persistence, preservation of
server IDs, and omission of generated IDs while the feature is disabled.

`prepare_conversation_items_for_history` is the single response-item ID
allocation boundary.

## Test plan

- `just test -p codex-features`
- `just test -p codex-core
response_item_ids_persist_across_resume_and_preserve_server_ids`
- `just test -p codex-core
non_openai_responses_requests_omit_item_turn_metadata`
- `just test -p codex-core
resize_all_images_prepares_failures_before_history_insertion`
- `just test -p codex-protocol`
- `just test -p codex-app-server-protocol`
- `just test -p codex-api azure_default_store_attaches_ids_and_headers`
## Summary

- skip the legacy `openai-curated` startup repository sync when remote
plugins are enabled and the current auth uses the Codex backend
- keep the curated sync for API-key, Bedrock, and unauthenticated
sessions that fall back to the local marketplace
- preserve configured marketplace upgrades and all remote plugin startup
warmups

## Why

The remote catalog owns plugin discovery and materialization only when
it is usable for the current auth mode. Starting the legacy curated
repository sync in that case performs an unnecessary Git/HTTP/archive
download and cache refresh. API-key and Bedrock sessions still require
the local curated marketplace, so they must continue syncing it.

## User impact

Codex startup no longer downloads or refreshes the local
`openai-curated` snapshot when the remote catalog is active. Behavior is
unchanged for auth modes that use the local curated marketplace.

## Validation

- `just fmt`
- `git diff --check`

Rust tests were not run per the repository's local verification policy
for this narrow conditional change.
## Summary
- expose `clock.curr_time` when current-time reminders are enabled
- query the session's configured time provider with the calling thread
id
- return the existing UTC reminder text for direct model calls
- return `{ "current_time": "YYYY-MM-DD HH:MM:SS UTC" }` in Code Mode

Clock lookup failures remain fatal, matching pre-inference reminder
behavior.

## Testing
- `just test -p codex-core current_time_tool_returns_the_latest_time`
- `just test -p codex-core
code_mode_current_time_returns_structured_result`
- `just fix -p codex-core`
## Why

Remote v2 compaction can return replacement-history items without IDs.
Because replacement history is installed directly, those items bypass
normal history preparation and remain ID-less in later Responses
requests even when the `item_ids` feature is enabled.

## What changed

- Pass the active `TurnContext` into `replace_compacted_history`.
- When `item_ids` is enabled, assign missing IDs before installing and
persisting replacement history.
- Rebuild `CompactedItem` from the prepared history so live and
persisted replacement histories match.
- Add integration coverage requiring IDs on every ID-capable input item
in the initial, remote v2 compaction, and post-compaction requests.

## Test plan

- `just test -p codex-core response_item_ids`
- `just test -p codex-core websocket_v2_test_codex_shell_chain`
- `just test -p codex-core remote_compaction_parity_pre_turn_auto`
- `just test -p codex-app-server
thread_inject_items_adds_raw_response_items_to_thread_history`
## Why

Plugin-install preflight and the actual OAuth login flow used different
discovery implementations. Preflight had a Codex-specific implementation
that only queried authorization-server metadata on the MCP host, while
login already used the upstream `rmcp` Rust MCP SDK. As a result,
servers that advertise a separate authorization server through RFC 9728
Protected Resource Metadata were classified as OAuth-unsupported during
plugin installation, so login was skipped.

## What changed

- delegate plugin-install OAuth discovery to
`rmcp::transport::AuthorizationManager`, the same implementation used by
the login flow
- let `rmcp` follow Protected Resource Metadata first and perform direct
RFC 8414 authorization-server discovery when protected-resource
discovery does not yield usable metadata
- retain Codex's existing HTTP headers, timeout, `no_proxy` behavior,
and scope normalization around that discovery
- add unit coverage and a pure-MCP plugin-install integration test that
proves the protected-resource path reaches OAuth client registration

This only changes shared MCP OAuth discovery. App declarations and
`appsNeedingAuth` behavior are unchanged.

## Verification

- `just test -p codex-rmcp-client auth_status`
- `just test -p codex-app-server plugin_install_starts_mcp_oauth`
- real plugin-install smoke test with an isolated `CODEX_HOME`: both
DigitalOcean MCP servers started OAuth callback listeners, while Linear
continued to start its existing direct-discovery OAuth flow
## Why

Remote environments can be registered before their exec-server is first
used. Starting the connection at registration time uses that startup
window, while sharing one startup result prevents background work and
capability calls from opening competing connections.

Keep initial startup simple: each environment makes one connection
attempt using its configured transport timeout. A failed initial attempt
is final for that environment, while an environment that disconnects
after connecting can still recover on a later operation.

## What changed

- Start URL and Noise environments in the background when they are added
to `EnvironmentManager`. Provider snapshots are fully validated before
connection work begins.
- Share one initial connection attempt and its saved result across
metadata, process, filesystem, and HTTP callers.
- Keep configured stdio environments lazy until first use so
registration does not launch a process.
- Tie background startup work to the environment lifetime so replacing
or dropping an environment cancels unfinished work.
- After an established client disconnects, share one fresh connection
attempt across concurrent callers. A failed attempt fails the current
operation without permanently preventing a later attempt.
- Store the shared lazy client directly on `Environment` and expose
small methods for starting, observing, and awaiting startup.

## Test plan

- `just test -p codex-exec-server`
- `just test -p codex-app-server
turn_start_resolves_sticky_thread_local_environment_and_turn_overrides`
## Why

Remote environments may still be resolving when Codex creates a session
or turn. Waiting for the existing all-or-nothing environment snapshot
can hold startup until the selected environment is usable.

Behind the default-off `deferred_executor` feature, let callers take a
useful snapshot immediately: completed environments remain available
normally, while unfinished environments are reported without blocking
startup. With the feature disabled, snapshots preserve the existing
blocking behavior.

Depends on openai#28674.

## What changed

- Store one ordered list of selected environments in
`ThreadEnvironments`. Each selection owns one shared resolution that
produces its complete `TurnEnvironment`.
- Start new resolutions in the background with `remote_handle()`,
allowing snapshots and the future wait tool to share the same result
while cancellation follows the retained handles.
- Make `snapshot()` a read-only operation: nonblocking snapshots collect
completed resolutions and retain handles for unfinished ones, while
blocking snapshots await every resolution.
- Replace completed failed resolutions from the current manager entry
and log when failed environments are omitted.
- Return attached and starting environments as a point-in-time view, and
count starting environments when deciding whether a snapshot is
local-only.
- Keep existing consumers attached-only. `to_selections()` derives from
attached environments, so child threads do not inherit an environment
that is still starting.

## Test plan

- `just test -p codex-core environment_selection`
- `just test -p codex-core
deferred_executor_reaches_model_before_remote_environment_is_ready`

## Landing note

Keep `deferred_executor` disabled for slow-starting executors until
configurable `environment/add` connection timeouts and caller support
land. When enabled, an environment that attaches after session startup
may remain absent from environment-derived model context, tools,
instructions, skills, and related state until follow-up refresh work
lands.
)

## Why

Remote environments registered through `environment/add` currently use
the fixed 10-second WebSocket connection timeout. Slow-starting
executors need a caller-selected connection window, but this should not
add retry policy or couple exec-server behavior to Core’s
`deferred_executor` feature.

Make the timeout an optional part of the existing experimental request.
Existing clients continue using the current default, while callers that
know an executor may take longer can request a larger window explicitly.

Depends on openai#28683.

## What changed

- Add optional `connectTimeoutMs` to `EnvironmentAddParams` and document
it in the app-server README.
- Pass the optional timeout through `EnvironmentRequestProcessor` into
one `EnvironmentManager::upsert_environment()` path; the manager applies
the existing default when it is omitted.
- Preserve the existing single-attempt lifecycle. The configured value
controls WebSocket connection and handshake time for both initial
connection and later reconnects; initialization retains its separate
timeout.
- Add an app-server integration test that sends the real JSON-RPC
request and verifies a stalled handshake observes the requested timeout.

## Test plan

- `just test -p codex-app-server-protocol`
- `just test -p codex-exec-server`
- `just test -p codex-app-server
environment_add_applies_connect_timeout`

## Rollout

This is additive and does not enable `deferred_executor`. Callers should
send a non-default timeout only after a compatible app-server is
deployed; omitted or `null` values retain the existing 10-second
default.
## Why

Multi-agent v2 currently carries an explicit-request-only delegation
rule in its static usage hint. That provides a safe default, but it
prevents clients from selecting proactive delegation per turn without
changing static guidance or rewriting prior model context.

This change makes delegation mode a session selection that can be
updated through `turn/start`, while deriving the effective model-visible
mode separately for each turn. Eligible multi-agent v2 turns remain
explicit-request-only unless proactive mode is both selected and
enabled.

## What changed

- Add the experimental `turn/start.multiAgentMode` parameter with
`explicitRequestOnly` and `proactive` values. Omission retains the
loaded session's current optional selection.
- Add the default-off `features.multi_agent_mode` feature gate. Eligible
multi-agent v2 turns use the selected mode when enabled; an unset
selection or disabled gate resolves to `explicitRequestOnly`.
- Treat mode prompting as inapplicable for multi-agent v1 and other
unsupported session configurations, producing no multi-agent mode
developer message rather than rejecting the turn.
- Move the explicit-request-only rule out of the static v2 usage hint
and into a bounded, tagged developer context fragment.
- Emit the effective mode in initial context and only when that
effective mode changes on later turns.
- Persist the effective mode in `TurnContextItem` as the durable
baseline for resume and context-update comparisons.

Historical rollout items are not rewritten. Later mode developer
messages establish the current rule incrementally.

## Not covered

- Initial selection through `thread/start` and selected-mode reporting
from thread lifecycle/settings APIs; those are isolated in the stacked
openai#28792.
- A TUI control or slash command for selecting the mode.
- Persisting a preferred mode to `config.toml`; selection remains
session/turn scoped.
- Changes to multi-agent concurrency limits, tool availability, or model
catalog capability declarations.
- Rewriting historical rollout prompt items. Cold resume restores the
latest persisted effective mode when available while leaving historical
developer messages intact.

## Verification

- `CARGO_INCREMENTAL=0 just test -p codex-core multi_agent_mode`
- Focused app-server coverage verifies that `turn/start.multiAgentMode`
produces proactive developer instructions for an eligible v2 turn.

## Stack

Followed by openai#28792, which adds `thread/start` initialization and
lifecycle/settings observability.
## Why

Once multi-agent mode can be selected per turn, clients also need to
choose the initial selection when creating a thread and observe that
selection through lifecycle and settings APIs.

The selected value is intentionally distinct from the effective
model-visible value: no client selection is represented as `null`, even
though an eligible multi-agent v2 turn derives `explicitRequestOnly` as
its effective default.

## What changed

- Add the optional experimental `thread/start.multiAgentMode` parameter
and pass it through thread creation.
- Preserve an omitted initial value as an unset selection rather than
eagerly storing `explicitRequestOnly`.
- Apply an explicit `thread/start` selection to the first turn through
the session configuration established at thread creation.
- Restore the latest persisted effective mode as the selected baseline
on cold resume when rollout history contains one.
- Inherit the optional selected mode from a loaded parent when creating
related runtime threads.
- Return the current selected `multiAgentMode` from `thread/start`,
`thread/resume`, `thread/fork`, and thread settings, using `null` when
no mode is selected.
- Keep lifecycle reporting independent from model capability and feature
eligibility; core turn construction remains responsible for calculating
and persisting the effective mode.

## Not covered

- Clearing an existing loaded-session selection back to unset through
`turn/start`; omitted or `null` currently retains the session's
selection.
- A TUI control, slash command, or `config.toml` preference.

## Verification

- `CARGO_INCREMENTAL=0 just test -p codex-app-server-protocol`
- `CARGO_INCREMENTAL=0 just test -p codex-app-server multi_agent_mode`

The focused app-server coverage verifies explicit `thread/start`
initialization, first-turn prompting, nullable reporting for an omitted
selection, and retention of selections that are not currently
runtime-eligible.

## Stack

Stacked on openai#28685. This PR contains only the thread initialization and
lifecycle/settings API layer.
…penai#28707)

## Stack

Depends on openai#28494.

## Description

This PR propagates shared rollout-budget exhaustion through the existing
`CodexErr::TurnAborted` task result.

Each thread records its model usage against the same ledger. Once the
ledger is exhausted, that usage update and all later usage updates
return `TurnAborted`. The task wrapper emits the normal aborted-turn
event and lifecycle instead of completing the turn.

This is intentionally a soft boundary: there is no cross-thread
`Op::Interrupt` fanout. An in-flight thread can finish its current
response before it observes the exhausted ledger, but every thread
aborts at its next usage-accounting boundary.

## Tests

The integration coverage verifies that:

- the response that exhausts the budget aborts its turn;
- a later response also aborts because the shared ledger remains
exhausted; and
- sub-agent usage draws from the same shared ledger; and
- local and remote-v2 compaction abort without retrying or emitting a
generic error.

Local checks:

- `just test -p codex-core
exhausted_budget_aborts_current_and_later_turns`
- `just test -p codex-core subagent_usage_draws_from_the_shared_budget`
- `just test -p codex-core
abort_regular_task_emits_marker_before_turn_aborted`
- `just test -p codex-core
compaction_budget_exhaustion_aborts_without_error_or_retry`
- `just fix -p codex-core`
- `just fmt`
- `git diff --check`

The full workspace test suite was not run locally.
Stacked on openai#28766.

## Why

Network approvals are environment-scoped: allowing a host in one
execution environment should not allow the same host in another
environment.

openai#28766 adds the inert IDs and constructor plumbing. This PR applies the
behavior on top.

## What changed

- Route managed network traffic through per-environment HTTP and SOCKS
proxy listeners.
- Stamp HTTP, HTTPS CONNECT, SOCKS TCP, and SOCKS UDP policy requests
with the source environment at the proxy boundary.
- Carry the selected execution environment through shell, unified exec,
zsh-fork, and sandbox transform paths.
- Include the environment in pending, approved-for-session, and
denied-for-session network approval cache keys.
- Include the environment in approval IDs and approval prompts.
- Preserve legacy fallback for unattributed requests, but deny when
active-call attribution is ambiguous.
- Fail closed if an environment-specific proxy endpoint cannot be
prepared.

## Validation

- just fmt
- CI will run tests and clippy
Adds a short AGENTS.md note asking reviewers to treat raw response item
events as compatibility-sensitive, even while they are experimental.

This keeps future app-server changes from accidentally breaking Codex
Cloud consumers of raw response item events.
## Summary

- Add `web_search = "indexed"` alongside `disabled`, `cached`, and
`live`.
- Use that same resolved mode for both hosted and standalone web search.
- For hosted search, send `index_gated_web_access: true` with external
web access enabled only when `indexed` is selected.
- For standalone search, preserve the existing boolean wire values for
existing modes (`cached` maps to `false` and `live` to `true`) and send
`"indexed"` only for `indexed`; `disabled` keeps the tool unavailable.
- Carry the mode through managed configuration requirements and
generated schemas.

## Why

Indexed search provides a middle ground between cached-only search and
unrestricted live page fetching. Search queries can remain live while
direct page fetches are limited to URLs admitted by the server.

The existing `web_search` setting remains the single source of truth, so
hosted and standalone executors cannot drift into different access
modes. Without an explicit `indexed` selection, the existing
model-visible tool and request shapes are unchanged.

```toml
web_search = "indexed"

[features]
standalone_web_search = true
```

## Validation

- `just fmt`
- `just test -p codex-api` (`126 passed`)
- `just test -p codex-web-search-extension` (`7 passed`)
- `just test -p codex-core
code_mode_can_call_indexed_standalone_web_search` (`1 passed`)
- Focused configuration, hosted request, standalone request, and
managed-requirement coverage is included in the PR; remaining suites run
in CI.

The full workspace test suite was not run locally.
## Why

Orchestrator-provided skills and Codex Apps MCP tools add model-visible
instructions, resources, and tools beyond the local workspace. Hosts
need config-level switches to disable those orchestrator-owned surfaces
independently, without disabling regular skills or regular MCP servers.

## What changed

- Adds `[orchestrator.skills].enabled` and `[orchestrator.mcp].enabled`
config entries, both defaulting to `true`.
- Includes the new settings in `config.schema.json` and in the config
lock so resolved thread configuration preserves the same orchestrator
exposure decisions.
- Threads `orchestrator.skills.enabled` through the app-server skills
extension so disabled orchestrator skills do not expose the `skills`
namespace or inject orchestrator skill context.
- Gates Codex Apps MCP exposure, app instructions, and app auth
eligibility on `orchestrator.mcp.enabled` while leaving non-Codex-Apps
MCP tools available.
- Updates the thread-manager sample config to disable both
orchestrator-owned surfaces.

## Verification

- Added config parsing, loading, defaulting, and schema coverage for the
new settings.
- Added MCP exposure coverage that `orchestrator.mcp.enabled = false`
removes Codex Apps tools while preserving regular MCP tools.
- Added app-server coverage that `orchestrator.skills.enabled = false`
prevents orchestrator skill tools, prompts, and resource reads from
reaching the model turn.
## Summary

- Remote unified-exec now sends the original command argv to exec-server
instead of materializing the orchestrator's sandbox wrapper first.
- Local unified-exec keeps the existing sandbox path unchanged.
- Add a focused regression test for a macOS-selected sandbox producing
plain remote argv.

Before:

    macOS orchestrator -> /usr/bin/sandbox-exec ... -> Linux exec-server

After:

    macOS orchestrator -> /bin/bash -lc pwd -> Linux exec-server

This is intentionally only the first cleanup step. Remote unified-exec
commands are sent without a process sandbox until the targeted
follow-ups below land. For the macOS-to-Linux path this is not a
practical regression: the old sandboxed attempt failed before process
launch because the Linux executor could not spawn macOS sandbox paths.

## Targeted follow-ups

1. Carry sandbox intent separately from argv.
   - Add an optional sandbox field to exec-server process params.
- Reuse FileSystemSandboxContext rather than introducing a new sandbox
model.
   - Carry managed-network enforcement as one explicit bit.
   - Keep argv plain.

2. Apply that intent inside exec-server.
   - Add a small process-start adapter before LocalProcess::exec.
- Reuse the existing codex-sandboxing SandboxManager and exec-server
runtime paths.
- Follow the same shape already used by exec-server filesystem
sandboxing.
   - Do not duplicate or move the sandbox implementations.

3. Report the sandbox actually used.
   - Return the executor-selected sandbox type from process/start.
- Use that value in core for sandbox-denial detection and retry
behavior.

## End state

The orchestrator sends plain commands plus portable sandbox intent. The
executor chooses and applies its own native sandbox: Linux executors use
Linux sandboxing, macOS executors use Seatbelt, and Windows executors
use Windows sandboxing. Concrete wrapper argv, helper paths, and sandbox
env markers never cross the executor boundary.
## Summary

- Rename the string-valued external web access enum variants from
`Offline` / `Online` to `Cached` / `Live`.
- Align the transport names with the existing `web_search = "cached"` /
`"live"` configuration vocabulary.

Existing behavior is unchanged: `WebSearchMode::Cached` and
`WebSearchMode::Live` continue to send the backward-compatible boolean
values `false` and `true`; `Indexed` remains the only mode currently
sent as a string.

## Validation

- `just fmt`
- `just test -p codex-api` (127 passed)
jif-oai and others added 25 commits June 25, 2026 10:31
…enai#28529)

## Why

openai#28522 routes selected-plugin HTTP MCP traffic through the owning
executor, but OAuth bootstrap and refresh still used host-local clients.
Executor-only servers therefore cannot complete discovery or login
through the same network boundary as the MCP connection.

## What changed

- adapt `codex_exec_server::HttpClient` to RMCP 1.8's `OAuthHttpClient`
contract
- let RMCP own discovery, dynamic registration, PKCE, token exchange,
and refresh
- route auth status, persisted-token startup, and app-server login
through the server runtime while preserving the existing local discovery
path
- add optional `threadId` to `mcpServer/oauth/login` and echo it in the
completion notification
- implement RMCP's redirect policy and 1 MiB OAuth response limit over
executor HTTP
- cover selected-thread OAuth discovery and login through an
executor-only route

Depends on openai#28522.
## Why

openai#28529 proves OAuth discovery uses the selected executor, but its
end-to-end test stops before the callback and token exchange.

## What changed

- add an executor-only mock token endpoint
- complete the OAuth callback using the authorization URL's `state` and
`redirect_uri`
- assert the PKCE token exchange reaches the executor-only endpoint
- assert the completion notification reports the selected thread and
succeeds

Depends on openai#28529.
We will drop support for this in the near future due to the complexity
it introduces.
…step (openai#29856)

## Why

`selectedCapabilityRoots` is durable thread intent: “use this capability
root from environment `worker`.”

The important product assumption is:

> One environment ID always names the same logical executor and stable
contents.

`worker` does not silently change from executor A to an unrelated
executor B. The process-local connection handle for `worker` can still
be replaced while Codex is running, though, for example when
`environment/add` registers a fresh handle for the same logical
environment.

The thread should persist only the stable selection. Each model step
should pair that selection with the exact ready handle captured for that
step.

## The boundary

```text
persisted thread intent
  plugin@1 -> environment "worker"
                |
                | capture the current step
                v
model-step view
  unavailable, or
  plugin@1 + worker's exact captured ready handle
```

The environment ID is the stable identity and cache key. The
`Arc<Environment>` is only a process-local handle retained so consumers
of one model step use the same captured environment. It is never
persisted and it does not imply different environment contents.

## What changes

### Persist the stable selection

Selected roots are written into `SessionMeta` and restored with the
thread. Forked subagents inherit the same selections, including
bounded-history forks.

Only stable data is persisted: root ID, environment ID, and root path.

### Capture readiness together with the exact handle

The environment snapshot records:

```rust
environment_id -> Some(Arc<Environment>) // ready in this step
environment_id -> None                   // still starting in this step
```

This prevents readiness and execution from coming from different
registry snapshots.

For example:

```text
step snapshot: worker -> handle A, ready
environment/add: worker -> fresh handle B for the same logical environment
current step: plugin@1 still uses captured handle A
```

Without carrying handle A in the snapshot, the resolver could combine “A
was ready” with handle B and treat B as ready before it had finished
starting.

This does not change cache invalidation. Stable capability metadata
remains identified by environment ID and capability root. Replacing a
process-local handle under the same stable environment ID does not
invalidate or rediscover that metadata.

### Resolve availability per model step

- A ready captured environment produces resolved roots using its
captured handle.
- A starting, missing, or failed environment is omitted from that step.
- A selected lazy environment that is outside the turn's captured
environment set is asked to start, and a later step can observe it as
ready.
- No capability files are scanned here.

Transient transport disconnects remain the remote client's reconnect
concern. This PR models initial attachment/readiness; it does not add
live socket-connectivity state.

## Example

```text
thread selection: plugin@1 -> environment "worker"

step 1: worker is starting -> plugin@1 unavailable
step 2: worker is ready    -> plugin@1 resolves through worker's captured handle
step 3: fresh local handle -> current step remains pinned; a later step captures its own view
```

Temporary unavailability does not discard the durable selection. Later
PRs can retain stable metadata caches while projecting only currently
available capabilities into model-visible World State.

## Compatibility

The app-server request shape does not change. Older rollouts without
`selected_capability_roots` deserialize to an empty list.

## Stack

1. **This PR:** persist stable selected roots and resolve them through
an exact model-step handle.
2. openai#29960: cache stable skill metadata and project available skills into
World State.
3. openai#29946: cache stable plugin declarations and manage the separate live
MCP runtime.
## Summary

- Record bounded connection, request, and process lifecycle metrics.
- Report active gauges from callbacks on every collection, including
delta exports.
- Serialize active-count updates so concurrent starts and finishes
cannot publish stale values.
- Serialize process exit, explicit termination, and shutdown through the
process registry so exactly one completion result wins.
- Keep the implementation small with single-owner RAII guards and one
real OTLP/HTTP integration test using the existing `wiremock`
dependency.

## Root cause

Process exit and session shutdown previously used cloned completion
state. That avoided duplicate emission, but it duplicated lifecycle
ownership and made the ordering harder to reason about. The process
registry mutex already defines the lifecycle ordering, so the final
implementation stores the metric guard and termination flag directly on
the process entry. Whichever path claims the entry first owns the
completion result.

Production metric export uses delta temporality. Event-only synchronous
gauge recordings disappear after the next collection when no count
changes, so active counts now use observable callbacks that report
current state on every collection.

The cleanup also removes the constant `result="accepted"` connection
tag, redundant route and response assertions, a custom HTTP collector,
and fallback initialization machinery that did not add behavior.

## Stack

Review and land this stack in order:

1. openai#27466 — trace exec-server JSON-RPC requests
2. openai#27467 — record bounded connection, request, and process lifecycle
metrics **(this PR)**
3. openai#27470 — observe remote registration and Noise rendezvous lifecycle

## Validation

- `just test -p codex-exec-server --lib` (158 passed)
- `just test -p codex-cli --test exec_server` (3 passed)
- `just test -p codex-otel
observable_gauge_is_collected_on_every_delta_snapshot` (1 passed)
- `CARGO_BUILD_JOBS=1 just fix -p codex-otel -p codex-exec-server`
- `just fmt`
- `git diff --check`
## Why

Helper threads such as task title generation can request a model ID that
is valid for the default OpenAI provider but unavailable from the active
provider. With Amazon Bedrock, `gpt-5.4-mini` is rejected while the
provider static catalog exposes Bedrock model IDs such as
`openai.gpt-5.5` and `openai.gpt-5.4`. This causes repeated background
404s and can surface a misleading turn error even when the main turn
succeeds.

Clients need an explicit way to ask app-server to resolve an unavailable
helper model to the active provider default. That fallback must remain
limited to providers with an authoritative static catalog so custom or
dynamically discovered model IDs are not rewritten based on an
incomplete catalog.

Fixes openai#28741.

## What changed

- Add the experimental `allowProviderModelFallback` option to
`thread/start`, defaulting to `false` to preserve existing behavior.
- Thread the option through thread creation and model selection.
- When enabled for a static model manager, preserve requested models
present in the catalog and replace unavailable models with the provider
default.
- Continue preserving explicit model IDs for dynamic model managers
without fetching a catalog solely to validate them.
- Document the new `thread/start` behavior in the app-server API
overview.

## Test
Temporary test-client harness:
```
ThreadStartParams {
    model: Some("gpt-5.4-mini".to_string()),
    allow_provider_model_fallback: true,
    ..Default::default()
}
```
Command:
```
CODEX_HOME=/tmp/codex-bedrock-thread-start-home \
CODEX_E2E_BEDROCK_THREAD_START_ONLY=1 \
./target/debug/codex-app-server-test-client \
  --codex-bin ./target/debug/codex \
  -c 'model_provider="amazon-bedrock"' \
  send-message-v2 --experimental-api ignored
```
Relevant output:
```
> "method": "thread/start",
> "params": {
>   "model": "gpt-5.4-mini",
>   "modelProvider": null,
>   "allowProviderModelFallback": true,
>   ...
> }

< "result": {
<   "model": "openai.gpt-5.5",
<   "modelProvider": "amazon-bedrock",
<   ...
< }
```
## Why

`codex sandbox` accepts a single named permissions profile, so the
existing plural `--permissions-profile` spelling is misleading. The
canonical flag and its help text should use the singular form without
breaking scripts that already use the old spelling.

## What changed

- Make `--permission-profile` the canonical flag for all sandbox
backends.
- Keep `--permissions-profile` as a hidden backwards-compatible alias.
- Cover the canonical spelling, legacy alias, and help visibility with
regression tests.

## Testing

Ran `just c sandbox --help` and verified I saw:

```shell
  -P, --permission-profile <NAME>
          Named permissions profile to apply from the active configuration stack
```
A zero interval lets callers request a reminder at every
otherwise-eligible inference boundary.

## Validation
- just test -p codex-core load_config_resolves_current_time_reminder
## tl;dr

Inject a `CODEX_PERMISSION_PROFILE` environment variable with the name
of the current permission profile when invoking a shell tool.

## Why

Shell tool owners may need to launch nested commands under the same
named permission profile, including through `codex sandbox -P PROFILE
--include-managed-config`. Until now, child processes could observe
sandbox and network metadata but could not identify the active named
permission profile.

The `--include-managed-config` flag is essential when a helper
reconstructs the sandbox from a profile name: it ensures the nested
sandbox also loads managed enterprise requirements. Without it, using
the inherited profile could unintentionally create a sandbox that does
not enforce the organization's managed restrictions.

The new environment value is intentionally informational and **must not
be treated as trusted input**. Any process in the ancestry can overwrite
an environment variable, so a consumer that passes this value to `codex
sandbox -P` must first validate it against the profiles that helper is
authorized to use.

## Example Use Case

Suppose an organization provides a trusted `remote-bash` wrapper that
lets Codex run a command on an approved build host. The local shell
command uses the named `:workspace` permission profile:

```toml
default_permissions = ":workspace"
```

The command exposed to the model is a small zsh wrapper. It deliberately
delegates with `exec`, preserving the original arguments and process
environment:

```zsh
#!/usr/bin/env zsh
exec /opt/codex-tools/remote_bash.py "$@"
```

The model invokes the public wrapper, not its Python implementation:

```sh
/opt/codex-tools/remote-bash \
  --host builder.example.com \
  -- printf '%s' 'hello world'
```

Only the inner implementation is authorized to escape the local sandbox:

```starlark
prefix_rule(
    pattern=["/opt/codex-tools/remote_bash.py"],
    decision="allow",
)
```

With zsh-fork, execution begins with `remote-bash` inside the
`:workspace` sandbox. When the wrapper calls `exec`, the exact prefix
rule matches `remote_bash.py`, so that inner script is restarted
unsandboxed. The escalated process inherits:

```text
CODEX_PERMISSION_PROFILE=:workspace
```

Inheritance does not make the value trustworthy. `remote_bash.py`
independently allowlists both the remote host and the permission profile
before using either value. In particular, a forged value such as
`:danger-full-access` is rejected before it can reach `codex sandbox
-P`:

```python
import argparse
import os
import shlex
import sys

ALLOWED_HOSTS = {"builder.example.com"}
ALLOWED_PROFILES = {":workspace"}

parser = argparse.ArgumentParser()
parser.add_argument("--host", required=True)
separator = sys.argv.index("--")
args = parser.parse_args(sys.argv[1:separator])
command = sys.argv[separator + 1:]

if args.host not in ALLOWED_HOSTS:
    parser.error("host is not allowlisted")
if not command:
    parser.error("the remote command must not be empty")

profile = os.environ.get("CODEX_PERMISSION_PROFILE")
if not profile:
    raise SystemExit("CODEX_PERMISSION_PROFILE must not be empty")
if profile not in ALLOWED_PROFILES:
    raise SystemExit("CODEX_PERMISSION_PROFILE is not allowlisted")

remote_command = shlex.join(command)
sandbox_command = shlex.join([
    "codex", "sandbox", "-P", profile,
    "--include-managed-config", "--",
    "bash", "-lc", remote_command,
])
print(shlex.join(["ssh", args.host, sandbox_command]))
```

This builds each command layer as an argument vector and uses
`shlex.join()` at the boundary, rather than interpolating untrusted
shell text. After validation and parsing, the nested command has this
structure:

```text
ssh argv:
  ["ssh", "builder.example.com", SANDBOX_COMMAND]

SANDBOX_COMMAND argv:
  ["codex", "sandbox", "-P", ":workspace",
   "--include-managed-config", "--",
   "bash", "-lc", "printf %s 'hello world'"]

bash -lc payload argv:
  ["printf", "%s", "hello world"]
```

A production implementation could execute that SSH command. The
integration fixture prints it and parses the result back into arguments,
verifying the complete flow:

```text
model invokes outer wrapper
  -> zsh-fork starts wrapper under :workspace
  -> wrapper execs allowlisted Python script
  -> prefix rule restarts Python script unsandboxed
  -> Python script inherits CODEX_PERMISSION_PROFILE=:workspace
  -> Python script verifies :workspace is allowlisted
  -> remote command runs codex sandbox -P :workspace
     with --include-managed-config
  -> nested sandbox honors managed enterprise requirements
```

This gives the trusted helper access to resources outside the local
sandbox—such as SSH credentials—while ensuring that it can select only
an explicitly authorized profile and that work on the remote host
remains subject to the organization's managed requirements.

## What changed

- Inject `CODEX_PERMISSION_PROFILE` after shell environment policy
evaluation so the active profile wins over inherited or configured stale
values.
- Apply the variable to both `shell_command` and unified `exec_command`,
including local, zsh-fork, and remote exec-server paths.
- Remove stale values when the session has no active named profile.
- Preserve the current profile value when loading a shell snapshot so a
parent snapshot cannot restore an older profile.

## Testing

- Added classic-shell integration coverage proving an exact prefix rule
can run a `require_escalated` script outside the `:workspace` sandbox
while preserving `CODEX_PERMISSION_PROFILE=:workspace`.
- Added zsh-fork integration coverage in which the model invokes an
outer zsh wrapper, an inner allowlisted `remote_bash.py` runs
unsandboxed, and its printed SSH command reconstructs the inherited
`:workspace` sandbox with `--include-managed-config` while preserving
every argument after `--`.
- The example helper treats `CODEX_PERMISSION_PROFILE` as untrusted and
validates it against `ALLOWED_PROFILES` before constructing the nested
command.
- Assert that the reconstructed sandbox command includes
`--include-managed-config` so nested use of the inherited profile cannot
bypass managed enterprise requirements.
- Added coverage for overriding and removing stale profile values.
- Verified `shell_command` receives the selected active profile.
- Added shell snapshot coverage using `printenv
CODEX_PERMISSION_PROFILE`.
```python
delivery_mode = "any_inference" # default
delivery_mode = "after_user_or_tool_output" # new mode
``` 

## Validation
- just test -p codex-core load_config_resolves_current_time_reminder
- just test -p codex-core
lock_contains_prompts_and_materializes_features
## Summary

- retry ERS `409 environment_offline` responses inside the existing
exec-server recovery loop
- keep all other registry conflicts terminal
- add focused coverage for both cases

## Root cause

When an exec server disconnects and reconnects, the client already
starts recovery and calls ERS `/connect`. During the transient executor
presence gap, ERS can return `409 environment_offline`. The retry
classifier treated every 409 as terminal, so the first response aborted
the existing 25-second recovery window before the executor came back
online. That then caused active processes to be marked lost.

This change classifies only the structured `environment_offline`
conflict as retryable. Recovery continues with the existing bounded
deadline, exponential backoff, and jitter.

## Validation

- `just test -p codex-exec-server client::recovery::tests` — 4 passed
- `just fix -p codex-exec-server` — passed
- `just fmt` — passed
- Full `just test -p codex-exec-server` reached unrelated macOS
filesystem-sandbox integration failures because nested
`/usr/bin/sandbox-exec` is denied in this environment (`sandbox_apply:
Operation not permitted`).
…ries (openai#30033)

## Summary
- track user-like input and tool-output boundaries in current-time
reminder state
- gate reminder injection when delivery_mode is
after_user_or_tool_output
- preserve interval debounce and forced reminders after context-window
changes

## Why
Training can request reminders only after user or tool-output items
while keeping the existing canonical pre-inference history-injection
path.

## Validation
- just test -p codex-core
current_time_reminders_can_follow_only_user_or_tool_outputs
- just test -p codex-core
current_time_reminders_follow_time_interval_and_persist_in_history
- just test -p codex-core
current_time_reminder_is_refreshed_after_compaction
- just fix -p codex-core
## Summary

- add an `EncodedFrame` type so IPC payloads are serialized and
size-checked before entering bounded queues
- add the V1 `operation/cancel` client-to-host message
- pin the new wire shape with protocol tests

## Why

The process-owned code-mode host needs bounded, pre-encoded outbound
messages and a best-effort cancellation signal. Keeping these wire
primitives in a protocol-only change lets their compatibility contract
be reviewed independently from either endpoint.

## Stack

This is **1 of 4** in the process-owned code-mode session stack. The
next PR targets this branch.

## Validation

- `just test -p codex-code-mode-protocol` — 22 passed
- `just fix -p codex-code-mode-protocol`
- `just fmt`
## Summary

- Record bounded duration and outcome metrics for remote environment
registration and Noise rendezvous connection attempts.
- Count reconnects by bounded reason: disconnect, connection failure, or
rejected registration.
- Trace registration at the owning client boundary without exporting raw
environment or registration identifiers.
- Replace the stale pre-Noise WebSocket observability design with the
current remote transport model.

## Stack

Review and land this stack in order:

1. openai#27466 — trace exec-server JSON-RPC requests
2. openai#27467 — record bounded connection, request, and process lifecycle
metrics
3. openai#27470 — observe remote registration and Noise rendezvous lifecycle
**(this PR)**

## Validation

- `just test -p codex-exec-server --lib` (149 passed)
- `just test -p codex-cli --test exec_server` (4 passed)
- `just argument-comment-lint`
- `just bazel-lock-check`
- `just fix -p codex-exec-server -p codex-cli`
- `just fmt`
## Summary

- make the external app-server time provider establish sleep deadlines
using `currentTime/read`
- poll the external clock once per second and complete `clock.sleep`
when the deadline is reached
- keep the system-clock timer and existing steer/agent-message
interruption behavior unchanged

## Why

This lets training control `clock.sleep` through its existing external
simulated clock without adding separate sleep/wake protocol methods.

## Testing

- `just fmt`
- `just test -p codex-app-server
external_sleep_polls_current_time_and_emits_items`
## Description

This makes Codex Apps tool reads use a shared in-memory snapshot instead
of rereading the disk cache every time `list_all_tools()` runs. Disk
still seeds the cache on startup and gets updated after successful
fetches, but it is no longer the live read path.

The core change is that `McpManager` now owns a process-scoped
`CodexAppsToolsCache`. Codex threads in the same app-server process now
share this Codex Apps in-memory tools snapshot. The snapshot is keyed by
the Codex home plus the Codex Apps identity: the active Codex auth
user/workspace and the effective Codex Apps MCP source config.

There's already code to hard-refresh the cache, so we respect it in this
PR.

## Local benchmark

I ran a local steady-state microbenchmark of the exact repeated Codex
Apps cached-tools read this PR removes, using the same real local cache
payload in both trees: `3,678,138` bytes and `381` tools. The cache file
was already warm in the OS page cache, so this measures same-process
reread/deserialization work rather than cold-disk latency or full turn
latency. Each run is 25 iterations (mimicking a turn that makes 25
inference calls).

| Version | Run 1 | Run 2 | Avg |
|---|---:|---:|---:|
| `origin/main` disk read + JSON deserialize + `filter_tools` | `50.755
ms` | `52.894 ms` | `51.825 ms` |
| This branch in-memory `current_tools` + `filter_tools` | `0.740 ms` |
`0.778 ms` | `0.759 ms` |

That removes about `51 ms` from each repeated Codex Apps cached-tools
read on this machine, roughly `68x` faster for that subpath. It is
useful evidence for the hot path this PR changes, but not a claim that
every production turn gets `51 ms` faster; end-to-end impact also
depends on the rest of `list_all_tools()` and tool-payload construction.

This is on my M2 Max macbook, so with a slower disk this would be much
worse (and indeed we did see this really blew up turn runtime with a
slow disk).
## Why

The patched zsh artifacts rarely change, but
`.github/workflows/rust-release-zsh.yml` currently runs as part of every
Rust release. Rebuilding the same four binaries for each Codex version
wastes release capacity and ties an independently versioned runtime
dependency to the main release cadence.

This establishes the producer side of a build-once flow. The existing
Rust release workflow remains unchanged until the first standalone
artifact release has been published and the checked-in DotSlash
manifests can be updated with its URLs and checksums.

## What changed

- Run the zsh release workflow for protected `codex-zsh-vX.Y.Z` tags
instead of as a reusable workflow.
- Validate the semantic release tag before starting the platform builds.
- Publish the four zsh archives to a GitHub prerelease so the release
never becomes the repository latest release.
- Publish the generated `codex-zsh` DotSlash manifest alongside the
archives.
- Document how to publish the next artifact version after changing the
pinned zsh commit or patch.

## Tag protection

An active repository tag ruleset named `codex-zsh-v*.*.*` targets
`refs/tags/codex-zsh-v*.*.*`. It restricts tag creation, updates,
deletion, and non-fast-forward changes; requires linear history; and
limits bypass to the configured repository role.

This was verified with:

```shell
gh api repos/openai/codex/rulesets/18140982
```

The response reported `"enforcement":"active"`, the expected tag
condition, and the `creation`, `update`, `deletion`, `non_fast_forward`,
and `required_linear_history` rules.

## Rollout

After this lands, publish the first `codex-zsh-vX.Y.Z` release. A
follow-up can then update the checked-in DotSlash manifests and remove
the zsh rebuild from `.github/workflows/rust-release.yml`.



---
[//]: # (BEGIN SAPLING FOOTER)
Stack created with [Sapling](https://sapling-scm.com). Best reviewed
with [ReviewStack](https://reviewstack.dev/openai/codex/pull/30114).
* openai#30116
* __->__ openai#30114
## Why

Once openai#30114 publishes zsh independently, regular Rust releases should
reuse that protected, versioned artifact set instead of rebuilding
identical zsh binaries for every Codex version. Keeping the zsh release
tag explicit in the workflow also makes future artifact upgrades
deliberate and easy to review.

This PR assumes the first standalone artifact release will be published
as `codex-zsh-v0.1.0` before this change lands.

## What changed

- Added `CODEX_ZSH_RELEASE_TAG` near the top of
`.github/workflows/rust-release.yml`, initially pinned to
`codex-zsh-v0.1.0`.
- Download the standalone release’s generated `codex-zsh` DotSlash
manifest before assembling Linux and macOS Codex packages.
- Added a `--zsh-manifest` package-builder override so release packaging
fetches the matching target archive and verifies the size and SHA-256
digest recorded in that manifest.
- Removed the reusable zsh build job from regular Rust releases.
- Stopped copying zsh archives into each Rust release and stopped
regenerating a zsh DotSlash manifest there.

Windows packaging remains unchanged because the patched zsh resource is
only shipped for supported Unix targets.

## Testing

- Added package-helper coverage that supplies a standalone manifest
override and verifies the extracted zsh bytes.
- Ran the `scripts/codex_package` unit test suite.
- Validated `.github/scripts/build-codex-package-archive.sh` with `bash
-n`.
…flicts

# Conflicts:
#	.codex/skills/remote-tests/SKILL.md
#	.github/dotslash-zsh-config.json
#	.github/scripts/build-codex-package-archive.sh
#	.github/workflows/v8-canary.yml
#	codex-rs/Cargo.lock
#	codex-rs/analytics/src/events.rs
#	codex-rs/app-server-protocol/schema/typescript/RealtimeConversationArchitecture.ts
#	codex-rs/app-server-protocol/schema/typescript/ResponseItemMetadata.ts
#	codex-rs/app-server-protocol/schema/typescript/v2/SkillMigration.ts
#	codex-rs/app-server-protocol/schema/typescript/v2/ThreadExtra.ts
#	codex-rs/app-server-protocol/schema/typescript/v2/WorkspaceMessageType.ts
#	codex-rs/app-server-protocol/src/protocol/v2/thread.rs
#	codex-rs/app-server-protocol/src/protocol/v2/turn.rs
#	codex-rs/app-server/README.md
#	codex-rs/app-server/src/current_time.rs
#	codex-rs/app-server/src/request_processors/thread_lifecycle.rs
#	codex-rs/app-server/src/request_processors/thread_processor.rs
#	codex-rs/app-server/src/request_processors/thread_summary.rs
#	codex-rs/app-server/src/request_processors/turn_processor.rs
#	codex-rs/app-server/src/thread_state.rs
#	codex-rs/app-server/tests/suite/v2/auto_env.rs
#	codex-rs/app-server/tests/suite/v2/client_metadata.rs
#	codex-rs/app-server/tests/suite/v2/current_time.rs
#	codex-rs/app-server/tests/suite/v2/mcp_server_elicitation.rs
#	codex-rs/app-server/tests/suite/v2/request_validation.rs
#	codex-rs/app-server/tests/suite/v2/turn_start.rs
#	codex-rs/cli/src/exec_server_telemetry.rs
#	codex-rs/cli/tests/exec_server.rs
#	codex-rs/code-mode-protocol/src/host/host_tests.rs
#	codex-rs/code-mode-protocol/src/host/message.rs
#	codex-rs/code-mode-protocol/src/host/mod.rs
#	codex-rs/code-mode-protocol/src/host/types.rs
#	codex-rs/codex-mcp/src/codex_apps.rs
#	codex-rs/codex-mcp/src/connection_manager.rs
#	codex-rs/codex-mcp/src/connection_manager_tests.rs
#	codex-rs/codex-mcp/src/mcp/auth.rs
#	codex-rs/codex-mcp/src/plugin_config_tests.rs
#	codex-rs/codex-mcp/src/rmcp_client.rs
#	codex-rs/codex-mcp/src/runtime.rs
#	codex-rs/core-plugins/src/loader.rs
#	codex-rs/core-plugins/src/manager_tests.rs
#	codex-rs/core-skills/src/loader.rs
#	codex-rs/core-skills/src/loader/environment.rs
#	codex-rs/core/config.schema.json
#	codex-rs/core/src/agent/control/spawn.rs
#	codex-rs/core/src/agent/control_tests.rs
#	codex-rs/core/src/agents_md.rs
#	codex-rs/core/src/agents_md_tests.rs
#	codex-rs/core/src/client.rs
#	codex-rs/core/src/client_tests.rs
#	codex-rs/core/src/compact_token_budget.rs
#	codex-rs/core/src/config/config_tests.rs
#	codex-rs/core/src/config/mod.rs
#	codex-rs/core/src/connectors_tests.rs
#	codex-rs/core/src/context/token_budget_context.rs
#	codex-rs/core/src/context/world_state/environment.rs
#	codex-rs/core/src/context/world_state/environment_render_tests.rs
#	codex-rs/core/src/context/world_state/environment_tests.rs
#	codex-rs/core/src/context/world_state/mod.rs
#	codex-rs/core/src/context_manager/history.rs
#	codex-rs/core/src/context_manager/history_tests.rs
#	codex-rs/core/src/context_manager/updates.rs
#	codex-rs/core/src/current_time.rs
#	codex-rs/core/src/event_mapping.rs
#	codex-rs/core/src/event_mapping_tests.rs
#	codex-rs/core/src/lib.rs
#	codex-rs/core/src/mcp_tool_call.rs
#	codex-rs/core/src/mcp_tool_call/telemetry.rs
#	codex-rs/core/src/mcp_tool_call/telemetry_tests.rs
#	codex-rs/core/src/mcp_tool_call_tests.rs
#	codex-rs/core/src/prompt_debug.rs
#	codex-rs/core/src/session/config_lock.rs
#	codex-rs/core/src/session/mcp.rs
#	codex-rs/core/src/session/mod.rs
#	codex-rs/core/src/session/multi_agents.rs
#	codex-rs/core/src/session/rollout_reconstruction.rs
#	codex-rs/core/src/session/rollout_reconstruction_tests.rs
#	codex-rs/core/src/session/session.rs
#	codex-rs/core/src/session/step_context.rs
#	codex-rs/core/src/session/tests.rs
#	codex-rs/core/src/session/tests/guardian_tests.rs
#	codex-rs/core/src/session/time_reminder.rs
#	codex-rs/core/src/session/turn.rs
#	codex-rs/core/src/session/turn_context.rs
#	codex-rs/core/src/session/world_state.rs
#	codex-rs/core/src/session_startup_prewarm.rs
#	codex-rs/core/src/state/service.rs
#	codex-rs/core/src/thread_manager.rs
#	codex-rs/core/src/thread_manager_tests.rs
#	codex-rs/core/src/thread_rollout_truncation_tests.rs
#	codex-rs/core/src/tools/handlers/current_time.rs
#	codex-rs/core/src/tools/handlers/multi_agents_tests.rs
#	codex-rs/core/src/tools/handlers/shell/shell_command.rs
#	codex-rs/core/src/tools/handlers/shell_tests.rs
#	codex-rs/core/src/tools/spec_plan.rs
#	codex-rs/core/src/tools/spec_plan_tests.rs
#	codex-rs/core/tests/common/test_codex.rs
#	codex-rs/core/tests/suite/agents_md.rs
#	codex-rs/core/tests/suite/apply_patch_cli.rs
#	codex-rs/core/tests/suite/code_mode.rs
#	codex-rs/core/tests/suite/current_time_reminder.rs
#	codex-rs/core/tests/suite/mcp_refresh_cleanup.rs
#	codex-rs/core/tests/suite/multi_agent_mode.rs
#	codex-rs/core/tests/suite/remote_env.rs
#	codex-rs/core/tests/suite/request_plugin_install.rs
#	codex-rs/core/tests/suite/token_budget.rs
#	codex-rs/exec-server-protocol/src/protocol.rs
#	codex-rs/exec-server-protocol/src/rpc.rs
#	codex-rs/exec-server/Cargo.toml
#	codex-rs/exec-server/src/client.rs
#	codex-rs/exec-server/src/environment.rs
#	codex-rs/exec-server/src/lib.rs
#	codex-rs/exec-server/src/local_process.rs
#	codex-rs/exec-server/src/rpc.rs
#	codex-rs/exec-server/src/server/process_handler.rs
#	codex-rs/exec-server/src/server/processor.rs
#	codex-rs/exec-server/src/server/session_registry.rs
#	codex-rs/ext/mcp/src/executor_plugin/provider.rs
#	codex-rs/ext/mcp/src/executor_plugin/provider_tests.rs
#	codex-rs/features/src/feature_configs.rs
#	codex-rs/features/src/lib.rs
#	codex-rs/login/src/auth/agent_identity.rs
#	codex-rs/login/src/auth/auth_tests.rs
#	codex-rs/login/src/auth/manager.rs
#	codex-rs/network-proxy/src/http_proxy.rs
#	codex-rs/network-proxy/src/proxy.rs
#	codex-rs/protocol/src/protocol.rs
#	codex-rs/rmcp-client/src/auth_status.rs
#	codex-rs/rollout/src/persistence_metrics.rs
#	codex-rs/rollout/src/policy.rs
#	codex-rs/rollout/src/recorder.rs
#	codex-rs/thread-store/src/local/create_thread.rs
#	codex-rs/tui/src/chatwidget/plugin_catalog.rs
#	codex-rs/tui/src/chatwidget/snapshots/codex_tui__chatwidget__tests__plugin_detail_popup_installed.snap
#	codex-rs/tui/src/chatwidget/snapshots/codex_tui__chatwidget__tests__plugins_popup_curated_marketplace.snap
#	codex-rs/tui/src/chatwidget/snapshots/codex_tui__chatwidget__tests__plugins_popup_newly_installed_marketplace.snap
#	codex-rs/tui/src/chatwidget/snapshots/codex_tui__chatwidget__tests__plugins_popup_search_filtered.snap
#	codex-rs/tui/src/chatwidget/tests/popups_and_settings.rs
#	codex-rs/tui/src/tui/test_support.rs
#	codex-rs/utils/plugins/src/plugin_namespace.rs
#	codex-rs/windows-sandbox-rs/src/elevated_impl.rs
#	codex-rs/windows-sandbox-rs/src/unified_exec/backends/elevated.rs
…nt/wallentx_termux-target_from_release_0.143.0_c13a6baae40b
@unemployabot
unemployabot Bot requested a review from wallentx June 26, 2026 04:39
@unemployabot unemployabot Bot added checkpoint Checkpoint merge termux-release Termux release automation labels Jun 26, 2026
@unemployabot
unemployabot Bot merged commit dc2b92a into wallentx/termux-target Jun 26, 2026
1 check passed
@unemployabot
unemployabot Bot deleted the checkpoint/wallentx_termux-target_from_release_0.143.0_c13a6baae40b branch June 26, 2026 04:39
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

checkpoint Checkpoint merge termux-release Termux release automation

Projects

None yet

Development

Successfully merging this pull request may close these issues.