checkpoint: into wallentx/termux-target from release/0.143.0 @ c13a6baae40b - #265
Merged
unemployabot[bot] merged 285 commits intoJun 26, 2026
Conversation
## 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)
…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
Termux rust-v0.143.0-alpha.25
…nt/wallentx_termux-target_from_release_0.143.0_c13a6baae40b
unemployabot
Bot
deleted the
checkpoint/wallentx_termux-target_from_release_0.143.0_c13a6baae40b
branch
June 26, 2026 04:39
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Termux release checkpoint
release/0.143.0c13a6baae40b2bb54333c2c790c27a911a730874wallentx/termux-targetThis PR carries release-train conflict fixes and follow-up changes back into the reusable Termux patch branch.
Release-only workflow files and metadata under
.githubwere restored to the destination branch versions before opening this PR.