mirror of
https://github.com/pchuan98/codex.git
synced 2026-07-01 00:31:56 +08:00
2e377ce5e5bc3da1ca2b133496cbccab3a3d2c01
7303 Commits
-
feat: make ThreadStore available on ThreadExtensionDependencies (#27439)
Generally useful for extensions.
Michael Bolin ·
2026-06-10 15:17:15 -04:00 -
[plugins] Inject remote_plugin_id into install elicitations (#26409)
Summary - Propagate cached remote plugin IDs through Codex plugin discovery. - Inject `remote_plugin_id` and connector IDs into `request_plugin_install` elicitation `_meta` from the resolved plugin. - Keep the remote plugin ID out of the model-facing tool schema, arguments, and result. Validation - `just test -p codex-tools` - `just test -p codex-core-plugins` - `just test -p codex-core list_tool_suggest_discoverable_plugins_includes_cached_remote_global_plugins` - `just fix -p codex-tools` - `just fix -p codex-core-plugins` - `just fix -p codex-core` - `git diff --check` - `just test -p codex-core` was also attempted: 2,581 passed, 55 failed, and 1 timed out across unrelated sandbox/environment-sensitive integration tests.
Alex Daley ·
2026-06-10 12:01:03 -07:00 -
[codex] extract external agent import picker renderer (#27065)
## Why The external-agent import picker is easier to review when its rendering refactor lands separately from new state and interaction behavior. This layer is intended to be behavior-neutral. ## What changed - extract external-agent migration rendering into a dedicated `render` module - preserve existing behavior while separating presentation from interaction logic - establish a smaller foundation for the import picker UX in the next PR ## Validation - `just test -p codex-tui external_agent_config_migration` (10 passed) ## Stack 1. [#27064](https://github.com/openai/codex/pull/27064): remove the startup migration flow 2. [#27065](https://github.com/openai/codex/pull/27065): extract the picker renderer 3. [#27070](https://github.com/openai/codex/pull/27070): add the external-agent import picker UX 4. [#27071](https://github.com/openai/codex/pull/27071): expose the flow through `/import` **This PR is stack item 2.** Draft while the lower stack dependency is reviewed.
stefanstokic-oai ·
2026-06-10 14:48:30 -04:00 -
[codex] Retry transient Guardian review failures (#27062)
## Background Codex can use **Auto Review** for permission requests. Instead of asking the user immediately, Codex starts a separate locked-down reviewer session called **Guardian**, which returns a structured `allow` or `deny` assessment. The Guardian reviewer is itself a Codex session, so its model request can fail for transient infrastructure reasons such as model overload, HTTP connection failure, or response-stream disconnect. Today, any such failure immediately ends the Auto Review attempt and blocks the action. This PR adds bounded retries for failures that the existing protocol explicitly identifies as transient. Linear context: [CA-539](https://linear.app/openai/issue/CA-539/retry-auto-review-infrastructure-failures-and-fall-back-to-manual) ## What changes A Guardian review can now make at most **three total attempts**: 1. Run the review normally. 2. Retry after a jittered delay of roughly 180–220 ms if the first attempt fails with an eligible error. 3. Retry after a jittered delay of roughly 360–440 ms if the second attempt also fails with an eligible error. All attempts share the original review deadline. Jitter spreads retries from concurrent clients to reduce synchronized load during broader outages. The retries do not reset the user's maximum wait time, and the backoff waits terminate early if the review is cancelled or the deadline expires. Before retrying, the existing Guardian session lifecycle decides whether the session remains usable. Healthy trunks are reused, broken trunks are removed by the existing cleanup path, and ephemeral sessions continue to clean themselves up. The review still emits one logical lifecycle to clients. Recoverable intermediate failures do not produce warnings or terminal events. ## Retry policy ### Retried up to twice - model/server overload - HTTP connection failure - response-stream connection failure - response-stream disconnect - internal server error - a final reviewer message that cannot be parsed as the required Guardian assessment ### Not retried - bad or invalid requests - authentication failures - usage limits - cyber-policy failures - errors without a structured category - a request that already exhausted the lower-level Responses retry budget - a completed Guardian turn with no assessment payload - prompt-construction failures - Guardian review timeout - cancellation or abort - a valid `deny` assessment The session-error classification uses `ErrorEvent.codex_error_info`; it does not inspect error-message strings. ## Implementation notes - `wait_for_guardian_review` preserves the complete `ErrorEvent`, including structured `codex_error_info`. - Guardian session failures preserve the original message and optional structured `CodexErrorInfo`. - The retry policy classifies the explicitly transient `CodexErrorInfo` variants; unknown, absent, and deterministic categories are not retried. - The Guardian session manager receives the caller's deadline rather than creating a new timeout per attempt. - Analytics record the final `attempt_count`. - Retry orchestration does not add a separate session-cleanup protocol; it relies on the existing trunk and ephemeral lifecycle decisions. ## Automated testing Focused Guardian coverage verifies: - every supported transient `CodexErrorInfo` is classified as retryable, while absent and non-transient categories are not; - structured transient session failure -> retry -> approval with the healthy trunk reused; - two invalid Guardian responses -> third attempt -> approval, with exactly three requests; - three invalid responses -> existing fail-closed result, with exactly three requests and one terminal lifecycle; - valid denial, missing payload, invalid request, timeout, cancellation, and prompt/session construction failures are not retried; - retry eligibility ends after the third attempt; - retry delays use the shared exponential backoff helper and remain within the expected jitter bounds; - cancellation and deadline expiry interrupt the backoff wait; - healthy trunks are reused across retryable failures; - broken event streams remove the trunk through the existing lifecycle cleanup; - an ephemeral retry does not disturb a concurrent trunk review. Validation performed: - `just test -p codex-core guardian_review_ guardian_ephemeral_retry_preserves_parallel_trunk_and_fork_history run_review_removes_trunk_when_event_stream_is_broken` — **42 passed**; - `just test -p codex-analytics` — **71 passed**; - scoped Clippy fixes for `codex-core` and `codex-analytics` passed. A prior full `codex-core` run had unrelated environment-sensitive failures outside Guardian coverage. ## Manual QA The focused integration tests use the local mock Responses server to inspect exact request counts and emitted lifecycle events. They confirm that retries are internal, a successful later attempt supplies the final decision, non-retryable failures issue only one request, and exhausted retries emit only one terminal result.
kbazzi ·
2026-06-10 11:46:57 -07:00 -
[codex] Raise app-server recursion limit (#27421)
## Summary Unblock Rust release builds after tracing instrumentation increased the async future query depth beyond rustc's default limit. Set the `codex-app-server` crate recursion limit to 256. This changes compilation only; runtime behavior is unchanged. ## Validation - `just test -p codex-app-server` - `cargo build --release --bin codex-app-server`
Adam Perry @ OpenAI ·
2026-06-10 11:37:14 -07:00 -
[codex] remove blocking external agent migration flow (#27064)
## Why External-agent import should be initiated deliberately instead of interrupting eligible TUI startups. This cleanup removes the blocking startup flow before the replacement import experience is introduced later in the stack. ## What changed - remove the startup-blocking external-agent migration prompt - remove the now-unused external migration feature gate - remove the obsolete TUI app-server migration wrappers - retain the dormant picker behind a module-scoped dead-code allowance until the next stack item wires it back in - keep normal TUI startup focused on entering Codex immediately ## Validation - `bazel build --config=clippy //codex-rs/tui:tui //codex-rs/tui:tui-unit-tests-bin` - `just test -p codex-tui external_agent_config_migration` (8 passed) - `just test -p codex-tui` (2,786 passed, 12 unrelated local environment-sensitive failures, 4 skipped) - `just fix -p codex-tui` - `just fmt` ## Stack 1. [#27064](https://github.com/openai/codex/pull/27064): remove the startup migration flow 2. [#27065](https://github.com/openai/codex/pull/27065): extract the picker renderer 3. [#27070](https://github.com/openai/codex/pull/27070): add the external-agent import picker UX 4. [#27071](https://github.com/openai/codex/pull/27071): expose the flow through `/import` **This PR is stack item 1.**
stefanstokic-oai ·
2026-06-10 14:25:04 -04:00 -
fix: Auto-recover from corrupted sqlite databases (#26859)
Further investigation of the sqlite incidents showed that the problems are due to corruption from the older version of SQLite that we recently upgraded, and that the data is truly corrupted in the root database -- recovery of all data is not possible. Given that the data is reconstructable from the rollouts on disk, we should just auto-backup the database and let codex rebuild the rollout info from the disk rollouts. The new behavior is that appserver auto-backs-up and rebuilds (with logs reflecting that behavior). The CLI now pops a message letting you know this happened and the paths of the backed-up corrupt db and the new database. There is also context added so that the desktop app can read the rebuild info from it and inform the user with it.
David de Regt ·
2026-06-10 11:24:29 -07:00 -
Add app-server
thread/deleteAPI (#25018)## Why Clients can archive and unarchive threads today, but there is no app-server API for permanently removing a thread. Deletion also needs to cover the full session tree: deleting a main thread should remove spawned subagent threads and the related local metadata instead of leaving orphaned rollout files, goals, or subagent state behind. ## What - Adds the v2 `thread/delete` request and `thread/deleted` notification, with the response shape kept consistent with `thread/archive`. - Implements local hard delete for active and archived rollout files. - Deletes the requested thread's state DB row as the commit point, then best-effort cleans associated state including spawned descendants, goals, spawn edges, logs, dynamic tools, and agent job assignments. - Updates app-server API docs and generated protocol schema/TypeScript fixtures.
Eric Traut ·
2026-06-10 11:22:12 -07:00 -
Add app-server background terminal process APIs (#26041)
## Summary Codex Apps needs app-server as the source of truth for chat-started background terminals instead of guessing from local process trees. This PR adds experimental v2 APIs to list and terminate background terminals for a loaded thread using app-server process ids, so clients can manage background terminals without local PID discovery. ## Changes - `thread/backgroundTerminals/list` returns paginated background terminal records with `itemId`, app-server `processId`, `command`, `cwd`, nullable `osPid`, nullable `cpuPercent`, and nullable `rssKb`. - `thread/backgroundTerminals/terminate` terminates one running background terminal by app-server `processId` and returns whether a process was terminated. - Background terminal list and terminate operations use unified-exec process manager state as their source of truth.
Eric Traut ·
2026-06-10 11:18:09 -07:00 -
[codex] Remove async_trait from ToolExecutor (#27304)
## Why We're now [discouraging use of `async_trait`](https://github.com/openai/codex/pull/20242). Removing use of `async_trait` from `ToolExecutor` yields a `codex_core` debug test build speedup of ~78% (from 227.5s to 50.3s) on my machine. Stacked on #27299, this PR applies the trait change after the handler bodies have been outlined. ## What Changed `ToolExecutor::handle` to return an explicit boxed `ToolExecutorFuture` instead of using `async_trait`. Updated ToolExecutor implementors to return `Box::pin(...)`, reexported the future alias through `codex-tools` and `codex-extension-api`, and removed `codex-tools` direct `async-trait` dependency.
Adam Perry @ OpenAI ·
2026-06-10 10:26:53 -07:00 -
Fix compressed rollout search path matching (#27407)
## Why `thread/search` found content inside compressed rollouts but could drop the result when joining it with SQLite-backed thread metadata. Search returned the physical `.jsonl.zst` path while SQLite retained the logical `.jsonl` path, so exact path matching failed. ## What changed - Key rollout search matches by their canonical logical `.jsonl` path, independent of the on-disk representation. - Canonicalize thread-list paths before joining them with content-search matches. - Update compressed-rollout coverage to assert the logical-path contract. ## Validation - Ran `just fmt`. - Ran `git diff --check`. - Tests and Clippy were intentionally left to CI.
jif ·
2026-06-10 19:23:42 +02:00 -
Index visible thread list ordering (#27391)
## Summary - add partial SQLite indexes for visible thread lists ordered by creation or update time - match the `archived` and non-empty `preview` filters used by `thread/list` - add query-plan coverage for both supported sort orders ## Query performance Benchmarked the production query shape on a snapshot of my database with ~10k threads before and after applying these indexes. The query selected the full thread projection with `archived = 0`, `preview <> ''`, the `openai` provider filter, and a page size of 201. Results are the mean of 30 runs after 5 warmups: | Query | Before | After | Speedup | | --- | ---: | ---: | ---: | | First page, `created_at_ms DESC` | 132.3 ms | 15.1 ms | 8.78x | | First page, `updated_at_ms DESC` | 123.6 ms | 15.5 ms | 7.99x | | Cursor page near row 4,000, `created_at_ms DESC` | 51.8 ms | 16.8 ms | 3.07x | | Cursor page near row 4,000, `updated_at_ms DESC` | 52.4 ms | 17.1 ms | 3.06x | Before this change, SQLite used `idx_threads_archived`, filtered the candidate rows, and built a temporary B-tree for the requested ordering. With the partial indexes, SQLite reads matching visible rows directly in timestamp order and stops at the page limit. `EXPLAIN QUERY PLAN` no longer reports `USE TEMP B-TREE FOR ORDER BY`. The result rows were identical before and after. The two partial indexes occupy approximately 168 KiB combined on this snapshot. ## Performance under contention I noticed this issue on a database with high-contention and tried to use simulated contention to validate the performance in that context. A synthetic SQLite benchmark ran five concurrent readers, matching the state database pool size, and fetched 101 rows per query. Results are the median of three runs on fresh copies of the same database snapshot: | Query | Before | After | | --- | ---: | ---: | | `created_at_ms` mean latency under saturation | 328 ms | 12 ms | | `created_at_ms` throughput | 16 queries/s | 412 queries/s | | `updated_at_ms` mean latency under saturation | 336 ms | 14 ms | | `updated_at_ms` throughput | 15 queries/s | 357 queries/s | For a burst of 100 queries queued through five connections, p95 completion time fell from 6.90 seconds to 226 ms for `created_at_ms`, and from 6.31 seconds to 473 ms for `updated_at_ms`. ## Validation - `just test -p codex-state` (135 tests passed) - query-plan regression covers created-at and updated-at ordering, requires the corresponding index, and rejects `TEMP B-TREE` - `just fmt`
Zanie Blue ·
2026-06-10 11:52:17 -05:00 -
[codex] Outline ToolExecutor handler bodies (#27299)
## Why We're now [discouraging use of `async_trait`](https://github.com/openai/codex/pull/20242). Removing use of `async_trait` from `ToolExecutor` yields a `codex_core` debug test build speedup of ~78% (from 227.5s to 50.3s) on my machine. For ease of reviewing, this is a prefactor to extract trait method implementations to inherent methods. This will prevent changing indentation from creating a huge diff. ## What Outlined existing `ToolExecutor::handle` bodies into inherent async `handle_call` methods across core and extension tool handlers. The trait methods still use `async_trait` and now delegate to `self.handle_call(...).await`; handler behavior is unchanged.
Adam Perry @ OpenAI ·
2026-06-10 09:40:41 -07:00 -
Reduce archive rollout lookup CPU (#27276)
## Why Archiving a thread can spike app-server CPU when the state DB does not have a usable rollout path. The archive path falls back to locating the rollout by thread id; because rollout filenames already contain the UUID, the cheap fallback should find the file directly before invoking broader file search. ## What Changed - In `codex-rs/rollout/src/list.rs`, try the exact rollout filename lookup before `codex-file-search`. - Keep fuzzy search as the final legacy fallback when no filename match is found. - Preserve the legacy fallback when the filename scan hits a traversal error, so an inaccessible stale subtree does not block lookup elsewhere. ## Verification - `just test -p codex-rollout` - `just test -p codex-thread-store` - `just test -p codex-app-server thread_archive`
Eric Traut ·
2026-06-10 09:28:12 -07:00 -
[codex] link Windows releases with LLD (#27315)
Windows x64 release builds spend about 36.5 of 48 minutes in final LLVM code generation and MSVC linking. Use the existing target-aware MSVC setup action to select LLD for release builds; the Windows ARM64 archive path already exercises the action and its LLD wrapper. In https://github.com/openai/codex/actions/runs/27242495616, macOS becomes the critical path after roughly four minutes of Windows improvement, so this is expected to reduce total workflow time by about four minutes.
Tamir Duberstein ·
2026-06-10 09:18:19 -07:00 -
[codex] add io PathUri native conversion APIs (#27280)
## Why Discovered some rough edges in the API while making use of it more widely within exec-server. It would be a lot more convenient for existing users of `AbsolutePathBuf` if `PathUri` conversion methods returned `std::io::Result`s. ## What * `PathUri::to_native_path()` -> `PathUri::to_abs_path()` * `PathUri::from_file_path()` -> `PathUri::from_abs_path()`
Adam Perry @ OpenAI ·
2026-06-10 08:51:17 -07:00 -
[codex] Store compact window id in rollout (#27264)
## Why Compaction window identity is part of session history, not model-client transport state. Persisting it with the compacted rollout item lets resumed threads continue from the reconstructed window without keeping mutable window state on `ModelClient`. ## What changed - Added `window_id` to `CompactedItem` and stamp it when `replace_compacted_history` installs compacted history. - Moved auto-compact window id ownership into `AutoCompactWindow` / `SessionState`; `ModelClient` now receives the request window id from callers instead of storing it. - Returned `window_id` from rollout reconstruction for resume. Reconstruction uses the newest surviving compacted item's stored `window_id` when present, and falls back to the legacy compacted-item count when it is absent. - Kept fork startup at the fresh default window id and updated direct model-client tests to pass explicit test window ids. ## Validation - `cargo check -p codex-core --tests`
pakrym-oai ·
2026-06-10 08:47:16 -07:00 -
Use latest-wins MCP manager replacement (#27259)
## Summary We originally addressed startup prewarming holding the read side of `RwLock<McpConnectionManager>` by snapshotting tool-list state. Review feedback identified the broader ownership problem: the outer synchronization should only publish or retrieve the current manager, while MCP operations rely on the manager's internal synchronization. A follow-up preserved operation retirement with a separate gate, but further review questioned whether that synchronization was actually required and whether we could support latest-wins replacement instead. This PR now stores the current MCP manager in `ArcSwap`. Each operation uses `load_full()` to obtain an owned `Arc<McpConnectionManager>`, then performs MCP I/O without retaining the publication mechanism. Refresh cancels obsolete startup work, constructs a replacement, and atomically publishes it. New operations see the latest manager, while operations that already loaded the previous manager retain a valid handle. Refresh happens at a turn boundary, so there should be no active user tool calls to drain. Git history supports dropping the outer `RwLock`. It was introduced in `03ffe4d595` on November 17, 2025 for non-blocking MCP startup: the session published an empty manager, startup initialized that same object while holding the write lock, and readers waited for initialization. `7cd2e84026` on February 19, 2026 removed that two-phase initialization in favor of constructing a fresh manager and swapping it in, explicitly noting that `Option` or `OnceCell` could replace the placeholder design. Hot reload later reused the existing lock to publish a replacement, but I found no indication that the lock was introduced to guarantee in-flight tool calls finish before refresh or shutdown. Terminal shutdown remains separate from refresh: it aborts startup prewarming and active tasks before shutting down the current manager, so tool calls may be interrupted and no model WebSocket work continues after shutdown. Focused regression coverage exercises pending tool-list cancellation, deferred refresh, and startup-prewarm shutdown.
Charlie Marsh ·
2026-06-10 08:33:21 -07:00 -
Remove async-trait from extension contributors (#27383)
## Why Extension contributors are registered behind `dyn Trait` objects, so native `async fn`/RPITIT methods would make these traits non-object-safe. Spell out the boxed, `Send` future contract directly so `extension-api` no longer needs `async-trait` while retaining the existing runtime model. ## What changed - add a shared `ExtensionFuture` alias and use it for asynchronous contributor methods - migrate production and test implementations to return `Box::pin(async move { ... })` - remove `async-trait` dependencies where they are no longer used, keeping it dev-only where unrelated test executors still require it ## Behavior No behavior change is intended. Contributor futures remain boxed, `Send`, dynamically dispatched, and lazily executed; cancellation and callback ordering stay unchanged. ## Testing - `just test -p codex-extension-api` (11 passed) - affected extension crates (64 passed) - targeted `codex-core` contributor tests (14 passed) - `just fmt` - `just bazel-lock-update` - `just bazel-lock-check` A broad local `codex-core` run compiled successfully but encountered unrelated sandbox and missing test-binary fixture failures; CI will run the full checks.jif ·
2026-06-10 14:31:09 +02:00 -
[codex] Tag multi-agent spawn metrics with version (#27375)
## Summary - tag legacy multi-agent spawn metrics with `version=v1` - tag multi-agent v2 spawn metrics with `version=v2` ## Why `codex.multi_agent.spawn` is emitted by both runtimes, so the existing metric cannot distinguish v2 adoption from aggregate multi-agent spawning. The bounded version tag makes that breakdown directly queryable without changing the counter's success-only semantics. ## Validation - `just fmt` - `git diff --check` - Tests and Clippy were intentionally left to CI.
jif ·
2026-06-10 13:06:48 +02:00 -
Use plugin-service MCP as the hosted plugin runtime (#27198)
## Stack - Base: #27191 - This PR is the third vertical and should be reviewed against `jif/external-plugins-2`, not `main`. ## Why #27191 moves the host-owned Apps MCP registration behind an extension contributor, but deliberately preserves the existing endpoint-selection feature while that contribution contract lands. App-server can therefore resolve the server through extensions, yet the hosted plugin endpoint is still selected through temporary `apps_mcp_path_override` plumbing. That is not the long-term plugin model. A plugin can bundle skills, connectors, MCP servers, and hooks, and those components do not all need the same source or execution environment. In particular, an authenticated HTTP MCP server can expose plugin capabilities directly from a backend without an executor or an orchestrator filesystem. This PR completes that hosted vertical. App-server's MCP extension now owns the aggregate hosted plugin runtime at `/ps/mcp`. Connector actions continue to arrive as MCP tools, while backend-provided skills arrive as MCP resources and use Codex's existing resource list/read paths. No second backend client, skill filesystem, or generic plugin activation framework is introduced. The backend route remains the hosted implementation. This change replaces Codex's temporary endpoint-selection mechanism, not the service behind the endpoint. ## What changed ### Hosted plugin runtime The MCP extension now contributes `codex_apps` as the hosted plugin runtime rather than as a configurable Apps endpoint: - `https://chatgpt.com` resolves to `https://chatgpt.com/backend-api/ps/mcp`; - a bare custom ChatGPT base resolves to `/api/codex/ps/mcp`; - the existing product-SKU header and ChatGPT authentication behavior are preserved; - executor availability is never consulted for this streamable HTTP transport. The same MCP connection carries both component shapes supported by the hosted endpoint: - connector actions are discovered and invoked as MCP tools; - hosted skills are enumerated and read as MCP resources through the existing `list_mcp_resources` and `read_mcp_resource` paths. This keeps component access in the subsystem that already owns the protocol instead of downloading backend skills into an orchestrator filesystem or inventing a parallel hosted-skill client. ### Explicit runtime ordering `McpManager` now resolves the reserved `codex_apps` entry in three ordered phases: 1. install the legacy Apps fallback for compatibility; 2. apply ordered extension `Set` or `Remove` overlays; 3. apply the final ChatGPT-auth gate without synthesizing the server again. This ordering is important: - an ordinary configured or plugin MCP server cannot claim the auth-bearing `codex_apps` name; - an extension-contributed hosted runtime wins over the fallback; - an extension `Remove` remains authoritative; - a host without the MCP extension retains the legacy Apps endpoint and current local-only behavior. The temporary `legacy_apps_mcp_loader_enabled` coordination flag is no longer needed. ### Remove the path override The `apps_mcp_path_override` feature and its runtime plumbing are removed, including: - the feature registry entry and structured feature config; - `Config` and `McpConfig` fields; - config schema output; - config-lock materialization; - URL override handling in `codex-mcp`. Existing boolean and structured forms still deserialize as ignored compatibility input. They are omitted from new serialized config, and config-lock comparison normalizes the removed input so older locks remain replayable. ### App-server coverage App-server MCP fixtures now serve the hosted route at `/api/codex/ps/mcp`. Existing resource-read and tool/elicitation flows therefore exercise the extension-owned endpoint rather than succeeding through the legacy fallback. The stack also adds the missing `codex_chatgpt::connectors` re-export for the manager-backed connector helper introduced in #27191. ## Compatibility - App-server installs the extension and uses `/ps/mcp` for the hosted runtime. - CLI and other hosts that do not install the extension retain the legacy Apps endpoint. - Apps disabled or non-ChatGPT authentication removes `codex_apps` from the effective runtime view. - Existing local plugins, local skills, executor-selected skills, configured MCP servers, and MCP OAuth behavior are otherwise unchanged. - Backend plugin enablement remains account/workspace state owned by the hosted endpoint; this PR does not add thread-local backend plugin selection. ## Architectural fit The stack now proves two independent runtime shapes: 1. #27184 resolves filesystem-backed skills through the executor that owns a selected root. 2. #27191 and this PR resolve a backend-hosted HTTP MCP through an extension with no executor. Together they preserve the intended separation: - selection identifies a plugin/root when explicit selection is needed; - each component's owning extension resolves its concrete access mechanism; - execution stays with the runtime required by that component; - existing skills, MCP, connector, and hook subsystems remain the downstream consumers. ## Planned follow-ups 1. **Executor stdio MCP:** selecting an executor plugin registers a manifest-declared stdio MCP server and executes it in the environment that owns the plugin. 2. **Optional backend selection:** only if CCA needs thread-local selection distinct from backend account/workspace enablement, add a concrete backend-owned capability location and surface those selected skills through the skills catalog. 3. **Connector metadata and hooks:** activate those plugin components through their existing owning subsystems, with executor hooks remaining environment-bound. 4. **Propagation and persistence:** define explicit resume, fork, subagent, refresh, and environment-removal semantics once selected roots have multiple real consumers. 5. **Local convergence:** migrate legacy local skill, MCP, connector, and hook paths behind their owning extensions one vertical at a time, then remove duplicate core managers and compatibility plumbing after parity. ## Verification Coverage in this change exercises: - extension-owned `/backend-api/ps/mcp` registration without an executor; - preservation of the legacy endpoint in hosts without the extension; - extension `Set` and `Remove` precedence over the legacy fallback; - ChatGPT-auth gating for the reserved server; - hosted MCP resource reads with and without an active thread; - connector tool invocation and MCP elicitation through the hosted route; - ignored boolean and structured forms of the removed path override; - config-lock replay compatibility for the removed feature. `cargo check -p codex-features -p codex-mcp-extension -p codex-app-server` passes. Tests and Clippy were not run locally under the current development instruction; CI provides the full validation pass.
jif ·
2026-06-10 12:54:21 +02:00 -
feat: keep child MCP warnings out of parent transcript (#27174)
## Why MCP startup status notifications are thread-owned, but `ChatWidget` trusted upstream routing. If routing state delivered a tagged child notification to the active parent widget, the child MCP failure could still mutate the parent's startup state and transcript. Rejecting it only inside the MCP handler was also too late because shared notification handling could already restore and consume the parent's retry status. ## What changed - Validate a tagged MCP status notification against the visible `ChatWidget` thread before shared notification handling mutates any parent state. - Cover child `Starting` and `Failed` notifications delivered to a retrying parent widget, asserting that they preserve its visible retry error and saved status header while producing no history or MCP status mutation. ## User impact Subagent MCP startup failures remain scoped to the child transcript instead of appearing as duplicate warnings in the parent transcript. ## Testing - `just test -p codex-tui mcp_startup_ignores_status_for_other_thread` - `just test -p codex-tui primary_thread_ignores_child_mcp_startup_notifications` - `just fmt`
jif ·
2026-06-10 11:45:49 +02:00 -
[codex] Make MCP connection startup fallible (#27261)
## Why Required MCP server startup was enforced in `Session::new` after `McpConnectionManager` had already created the clients. That split let other manager construction paths bypass the same requirement and exposed manager internals solely so the session could validate them. Keeping required-server readiness in the constructor gives every caller one consistent startup contract. ## What changed - make `McpConnectionManager::new` return `anyhow::Result<Self>` and fail when an enabled, required server cannot initialize - pass the startup cancellation token into the constructor so required-server waits remain cancellable - propagate constructor failures through resource reads, connector discovery, and MCP status collection - preserve the active manager and cancellation token when a refreshed replacement fails - keep required-startup failure collection private and cover the constructor error contract directly ## Validation - updated the focused connection-manager test to assert the complete required-server startup error - local tests not run; relying on CI
Ahmed Ibrahim ·
2026-06-10 00:17:58 -07:00 -
Add spans to run_turn (#27107)
## Why Codex app-server latency traces do not granularly cover turn orchestration, sampling-request preparation, and tool-loading work. These spans help separate local coordination/setup costs from model streaming and tool execution. ## What changed - Add `run_turn.*` spans around sampling-request input preparation and post-sampling state collection - Add function-level trace spans around turn setup, hook execution, compaction, prompt construction, and MCP tool exposure - Add `built_tools.*` spans around plugin loading and discoverable-tool loading ## Verification Trigger Codex rollout and observe new spans are included
mchen-oai ·
2026-06-10 04:41:06 +00:00 -
[codex] Fix post-merge analytics integration failures (#27285)
## Why Recent merges left `main` with analytics integration build failures. Local Cargo runs also made the trimmed-skills test depend on developer-installed skills, while Bazel used an isolated home. ## What changed - Clone `thread_metadata.thread_source` when constructing goal analytics event parameters. - Group app-server thread extension inputs into `ThreadExtensionDependencies`. - Isolate the trimmed-skills test home so its exact fixture count is stable across Cargo and Bazel. ## Validation - `cargo check -p codex-analytics` - `just test -p codex-analytics` (71 tests) - `just test -p codex-app-server` (837 tests; one unrelated zsh-fork timeout passed on retry)
Adam Perry @ OpenAI ·
2026-06-09 20:52:09 -07:00 -
[codex-analytics] emit goal lifecycle analytics (#27078)
## Why - Currently, there is no analytics event for `/goal` behavior - Existing events cannot identify goal execution or its resulting outcome - The original update in [#26182](https://github.com/openai/codex/pull/26182) was implemented before `/goal` moved into `codex-goal-extension`. ## What Changed - Adds `codex_goal_event` serialization and enrichment to `codex-analytics` - Emits goal events from the canonical `codex-goal-extension` mutation and accounting paths: - `created` when a new logical goal is persisted - `usage_accounted` when cumulative goal usage is persisted - `status_changed` when the stored goal status changes - `cleared` when the goal is deleted - Preserves causal `turn_id` for turn driven events and uses null attribution for external or idle lifecycle events - Changes goal deletion to return the deleted row so `cleared` retains the stable goal ID ## Event Details Includes standard analytics metadata along with goal specific fields: - `goal_id`: Stable ID stored in the local SQLite goal row and shared across the goal's events - `event_kind`: Observed operation (see the 4 lifecycle events cited in the above bullet) - `goal_status`: Resulting or last stored status: `active`, `paused`, `blocked`, `usage_limited`, etc. - `has_token_budget`: Indicates whether a token budget is configured - `turn_id`: Causal turn ID, or null when no causal turn exists - `cumulative_tokens_accounted`: Cumulative tokens on `usage_accounted` events; null otherwise - `cumulative_time_accounted_seconds`: Cumulative active time on `usage_accounted` events; null otherwise ## Validation - `just test -p codex-analytics -p codex-state -p codex-goal-extension` - `just test -p codex-core -E 'test(/goal/)'` - `just test -p codex-app-server` - `cargo build -p codex-analytics -p codex-core -p codex-state -p codex-app-server`
marksteinbrick-oai ·
2026-06-09 18:45:54 -07:00 -
Add per-session realtime model and version overrides (#24999)
## Why Clients need to select a realtime session configuration for an individual start without rewriting persisted configuration or restarting the app-server process. ## What Changed - Add optional `model` and `version` fields to `thread/realtime/start` - Forward those optional values through the realtime start operation and apply them only for that session - Preserve existing configured/default behavior when the new fields are omitted - Update generated protocol schema and app-server documentation ## Validation - Added/updated protocol serialization coverage for the new optional request fields - Added focused core coverage for a session override taking precedence over configured realtime selection - Added focused app-server coverage that a request override reaches the realtime WebSocket handshake
guinness-oai ·
2026-06-09 17:54:32 -07:00 -
Add spans to build_tool_router (#27094)
## Why - Local profiling shows `append_tool_search_executor` averages ~113ms per call. Adding a span lets us track this cost as we optimize in follow-up PRs, either by reducing the work or avoiding repeated rebuilds when inputs have not changed. - While we're here, we can add spans to `build_tool_router` and other sub-calls which code analysis shows may have additional opportunities for improvement. ## What changed Add function-level trace spans around `build_tool_router`, `build_tool_specs_and_registry`, `add_tool_sources`, `append_tool_search_executor`, and `build_model_visible_specs_and_registry` ## Verification Trigger Codex rollout and observe new spans are included
mchen-oai ·
2026-06-09 17:49:59 -07:00 -
feat: use provider defaults for memory models (#27129)
## Why Memory startup used hardcoded OpenAI model slugs for extraction and consolidation. That works for the default OpenAI-compatible path, but provider-specific backends can require different model identifiers. In particular, Amazon Bedrock should use its Bedrock model ID for these background memory requests instead of the OpenAI `gpt-5.4-mini` / `gpt-5.4` slugs. ## What Changed - Added provider-owned preferred memory model methods alongside `approval_review_preferred_model`. - Updated memory extraction and consolidation to resolve their default model through the active `ModelProvider`. - Added Amazon Bedrock overrides so both memory stages use `openai.gpt-5.4` through Bedrock’s provider-specific model ID. - Kept explicit `memories.extract_model` and `memories.consolidation_model` config overrides taking precedence. - Added startup coverage for default OpenAI and Bedrock memory model selection. #closes #26288
Celia Chen ·
2026-06-09 23:49:09 +00:00 -
canvrno-oai ·
2026-06-09 16:34:38 -07:00 -
[codex] Tighten MCP connection manager API visibility and order (#27257)
## Summary - order `McpConnectionManager` methods by visibility, with the primary constructor and public API first - restrict `list_available_server_infos` to `codex-mcp` - make `new_uninitialized` a private test-only helper ## Why The manager exposed methods that are only used inside `codex-mcp` or its unit tests. Tightening those methods keeps the exported API intentional, while the new ordering makes the supported surface easier to scan. ## Validation - `just fmt` - `git diff --check` - local tests not run; relying on CI
Ahmed Ibrahim ·
2026-06-09 16:07:34 -07:00 -
[codex] Retry streamable HTTP initialize failures (#25147)
## Summary - Retry transient streamable HTTP failures during RMCP startup when the failure happens while sending the initialize request. - Retry transient streamable HTTP failures for tools/list, which is read-only and safe to replay. - Cover both retryable HTTP statuses and request-layer failures where no HTTP status is returned. - Surface retryable HTTP statuses from the streamable HTTP adapter as typed client errors. - Add integration coverage for initialize retry, tools/list retry, no-status request failure retry, and non-retryable initialize status. ## Root cause The observed codex_apps failures can happen before normal tool execution: RMCP startup fails while sending initialize, or the first read-only tools/list fails after startup. Retrying hosted_apps_bridge tools/call would not cover initialize and would risk replaying side-effecting tool calls. This change retries the streamable HTTP handshake itself, recreates the transport between initialize attempts, and retries only tools/list among post-initialize service operations. ## Validation - cargo fmt --package codex-rmcp-client - cargo test -p codex-rmcp-client --test streamable_http_recovery
ssetty-oai ·
2026-06-09 15:49:48 -07:00 -
[2/4] Add private Python goal operations (#27111)
## Why The Python SDK must treat the runtime's initial goal turn and its continuations as one logical operation. That requires a private lifecycle engine before the public API can return the existing turn handle and result types. ## What - start goals by composing the existing clear/set goal RPCs - enforce persisted, idle threads and a bounded startup handshake - coalesce continuation notifications under a stable logical turn ID - aggregate items, usage, timing, and terminal status - support rollover-aware steering, interruption, cancellation, and cleanup - provide equivalent sync and async internals This is the second PR in the stack and intentionally adds no public API. ## Test plan - online CI, including the Python SDK suite - behavioral coverage is added in the following two stack PRs
Ahmed Ibrahim ·
2026-06-09 15:32:15 -07:00 -
Stop mirroring Codex user input into realtime (#27116)
## Why The realtime frontend model and the backing Codex thread should present one coherent assistant. Raw typed messages, steers, and worker reports belong to the orchestrator; the frontend model should receive the orchestrator's user-facing result rather than a second copy of those inputs. Today normal `turn/start` input is automatically inserted into the realtime conversation, while `turn/steer` is not. Besides creating inconsistent context, this can make the frontend model react independently before Codex has produced the response it should speak. ## What changed - Remove automatic accepted-user-input mirroring into realtime - Remove the mirror-only echo-suppression flag and dead V2 prefix helper - Preserve explicit app-to-realtime text injection and FEM-to-Codex delegation - Replace the positive mirror tests and obsolete snapshots with a negative routing regression test ## Test plan - `cargo test -p codex-core conversation_user_text_turn_is_not_sent_to_realtime` - `cargo test -p codex-core conversation_startup_context_is_truncated_and_sent_once_per_start` - `cargo test -p codex-core inbound_handoff_request_starts_turn`
guinness-oai ·
2026-06-09 15:20:01 -07:00 -
[codex] Handle Ctrl-C for non-TTY unified exec (#26734)
## Why A long-running unified exec process started with `tty: false` could not be interrupted via `write_stdin`: ordinary non-TTY stdin writes are rejected once stdin is closed, but an exact U+0003 payload should still map to a process interrupt. The interrupt should flow through the same process lifecycle path as a real signal so Codex preserves process-reported output and exit metadata instead of fabricating a Ctrl-C exit code or tearing down the session early. ## What Changed - Add `process/signal` to exec-server with `ProcessSignal::Interrupt` and an empty response. - Add a non-consuming `ProcessHandle::signal` path for spawned processes; on Unix it sends SIGINT to the process group and leaves terminate/hard-kill unchanged. - Route non-TTY U+0003 `write_stdin` through `process.signal(...)` instead of `terminate`, then let the normal post-write collection path drain output and observe exit. - Add exec-server coverage where a shell `trap INT` handler prints the signal and exits with its own code. - Add unified exec coverage where a `tty: false` process traps SIGINT, emits output, and exits with its own code. ## Validation - `just test -p codex-exec-server exec_process_signal_interrupts_process` - `just test -p codex-exec-server` - `just test -p codex-core write_stdin_ctrl_c_interrupts_non_tty_session`
pakrym-oai ·
2026-06-09 15:10:17 -07:00 -
[codex] Report unusable MCP OAuth credentials as logged out (#26713)
## Why Persisted MCP OAuth credentials were reported as authenticated whenever a credential record existed. An expired token without a usable refresh token could therefore appear as `OAuth` even though startup could not authenticate with it, leaving users with a misleading status instead of a login prompt. ## What changed - Classify stored OAuth credentials as missing, usable, or requiring authorization. - Reuse the existing refresh window so near-expiry credentials without a refresh path are also treated as logged out. - Validate required credential fields before reporting OAuth authentication. - Add unit coverage for credential usability and integration coverage for expired, unexpired, and refreshable persisted credentials. ## Validation - `just test -p codex-rmcp-client`
Adam Perry @ OpenAI ·
2026-06-09 14:18:24 -07:00 -
[codex] Characterize global instruction lifecycle (#26830)
## Why Global instruction behavior spans thread creation, resume, forks, subagents, and compaction. Characterization coverage is needed before changing those semantics so preserved history can be distinguished from newly loaded configuration. ## What changed - Extends the existing `agents_md` suite with fresh-thread, warning, resume, fork, and subagent lifecycle coverage. - Extends the existing `compact` suite with manual, mid-turn, and remote-v2 compaction coverage. - Asserts rendered instruction fragments, reported source paths, and structured request history before and after instruction-file mutations.
Adam Perry @ OpenAI ·
2026-06-09 20:51:57 +00:00 -
Route hosted Apps MCP through extensions (#27191)
## Stack - Base: #27184 - This PR is the second vertical and should be reviewed against `jif/external-plugins-1`, not `main`. ## Why CCA is moving toward a split runtime where the orchestrator may have no filesystem or executor, but it still needs to activate remotely hosted plugin components. HTTP MCP servers are the simplest complete example: they need configuration and host authentication, but they do not need an executor process. The Apps MCP endpoint is currently synthesized by a special-purpose loader inside the MCP runtime. That works locally, but it leaves hosted MCP activation outside the extension model being established in #27184. It also makes the Apps path a poor foundation for plugins whose skills, MCP servers, connectors, and hooks may come from different sources or execute in different places. This PR moves that one behavior behind an extension-owned contribution while preserving the existing local fallback. It deliberately does not introduce a generic plugin activation framework. ## What changed ### MCP extension contribution `codex-extension-api` gains an ordered `McpServerContributor` contract. A contributor returns typed `Set` or `Remove` overlays for MCP server configuration; later contributors win for the names they own. The contract stays at the existing MCP configuration boundary. Extensions do not create a second connection manager or transport abstraction. ### Hosted Apps MCP extension A new `codex-mcp-extension` contributes the reserved `codex_apps` server from the existing Apps feature, ChatGPT base URL, path override, and product SKU configuration. When `apps_mcp_path_override` is enabled for `https://chatgpt.com`, the resulting streamable HTTP endpoint is `https://chatgpt.com/backend-api/ps/mcp`. The existing ChatGPT-auth gate remains authoritative, so this server can run in an orchestrator-only process without being exposed for API-key sessions. ### One resolved runtime view `McpManager` now distinguishes three views: - **configured:** config- and plugin-backed servers before extension overlays; - **runtime:** configured servers plus host-installed extension contributions; - **effective:** runtime servers after auth gating and compatibility built-ins. App-server installs the hosted MCP extension and uses the runtime view for thread startup, refresh, status, threadless resource reads, connector discovery, and MCP OAuth lookup. This keeps `mcpServer/oauth/login` consistent with the servers exposed by the other MCP APIs. The hosted Apps server itself continues to use existing ChatGPT host authentication rather than MCP OAuth. ## Compatibility Hosts that do not install the MCP extension retain the existing Apps MCP synthesis path. This preserves current local-only, CLI, and standalone-host behavior while app-server exercises the extension path. Disabling Apps removes the reserved `codex_apps` entry, and losing ChatGPT auth removes it from the effective runtime view. Executor availability is not consulted for this HTTP transport. ## Follow-ups The next vertical will resolve a manifest-declared stdio MCP server from an executor-selected plugin root and execute it in the environment that owns that root. Later verticals can add backend-owned skills, connector metadata, hooks, durable selection semantics, and incremental local convergence without changing the component-specific runtime boundaries introduced here. ## Verification Focused coverage was added for: - contributing the hosted Apps MCP at `/backend-api/ps/mcp` without an executor; - requiring ChatGPT auth in the effective runtime view; - removing a reserved configured Apps server when the Apps feature is disabled. `cargo check -p codex-app-server -p codex-mcp-extension -p codex-extension-api -p codex-mcp` passed. Tests and Clippy were not run locally under the current development instruction; CI provides the full validation pass.
jif ·
2026-06-09 22:44:16 +02:00 -
[1/4] Add Python goal routing foundation (#27110)
## Why Goal continuation turns are emitted by the existing runtime as separate physical turns. The Python SDK needs private thread-scoped routing before it can present those notifications as one logical operation, without changing ordinary turn routing or the app-server protocol. ## What - add private goal operation state and thread-scoped notification routing - add internal wrappers for the existing `thread/goal/clear` and `thread/goal/set` RPCs - include existing goal notifications in the SDK notification union - preserve ordinary turn-ID routing unchanged - add focused routing coverage This PR does not expose a public goal API. It is the first PR in the Python goal operations stack. ## Test plan - online CI, including the Python SDK suite - focused typed-notification routing coverage
Ahmed Ibrahim ·
2026-06-09 13:35:29 -07:00 -
Reduce TUI legacy core dependencies (#26711)
## Why The TUI still reached through `app-server-client::legacy_core` for thread-name normalization and project-instruction filename details. In particular, checking the TUI's local filesystem for `/init` is incorrect for remote app-server sessions, where the server owns the working directory and instruction discovery. ## What changed - use the instruction source paths supplied by the app server to decide whether `/init` should avoid overwriting project instructions - keep the small thread-name normalization helper local to the TUI - remove the now-unused instruction filename constants, utility module, and other unused `legacy_core` re-exports - make status helper tests independent of concrete instruction filenames ## Verification - `just test -p codex-app-server-client` - `just test -p codex-tui slash_init_skips_when_project_instructions_are_loaded` - `just test -p codex-tui` ran 2,799 tests; 2,797 passed and two unrelated guardian feature-flag tests failed reproducibly in untouched code ### Manual test Started an app server over WebSocket with a remote workspace containing `AGENTS.md`, then connected the TUI using `--remote`. After confirming `thread/start` returned the file in `instructionSources`, deleted `AGENTS.md` and ran `/init` in the existing session. The TUI still reported that project instructions already existed and skipped `/init`. The trace contained no `turn/start` request, confirming the decision came from app-server session state rather than a new client-local filesystem check.
Eric Traut ·
2026-06-09 13:26:00 -07:00 -
Allow creating a new goal after completion (#26681)
## Why Users have indicated that they want an agent to be able to create a new goal for itself after completing the previous goal. Currently, that's not possible because agents cannot overwrite an existing goal even if it's complete. This PR removes this limitation and allows `create_goal` to overwrite an existing goal if it is in the `complete` state. ## What changed `create_goal` now replaces the existing goal only when its status is `complete`. The replacement is performed atomically in the goal store, creates a fresh active goal with reset usage, and continues to reject creation while any unfinished goal exists. App server clients see a single `thread/goal/updated` event when the previous goal is replaced with the new one. The tool description and error message now reflect these semantics. ## What didn't change Agents are not allowed to create a new goal (overwrite their existing goal) if an existing goal is still active, blocked, paused, or in any other state other than "completed".
Eric Traut ·
2026-06-09 13:23:31 -07:00 -
Add SOCKS5 TCP MITM coverage (#22685)
## Summary - reuse the MITM HTTPS serving path for raw SOCKS5 TCP streams - route limited-mode and hooked SOCKS5 TCP requests through MITM before dialing upstream - keep SOCKS5 UDP limited-mode behavior unchanged ## Validation - `just fmt` - `just test -p codex-network-proxy` - `just fix -p codex-network-proxy` - `git diff --check`
Winston Howes ·
2026-06-09 13:08:01 -07:00 -
Eric Ning ·
2026-06-09 12:52:05 -07:00 -
[codex] Speed up local nextest runs (#26479)
## Why `just test` currently uses the CI-oriented nextest profile, which serializes app-server integration tests even on developer machines that can run several safely. Bounded local parallelism substantially shortens this common iteration loop without changing CI behavior. Eight-worker experiments were faster, but keeping them reliable required relaxing several test deadlines. Four workers for integration tests is a solid tradeoff that speeds up local testing without needing to change test logic. ## What changed - Add a `local` nextest profile that inherits the existing defaults. - Allow up to four app-server integration tests to run concurrently under that profile. - Make `just test` select the local profile on Unix and Windows. - Keep the default CI profile serialized and leave all test deadlines unchanged. The tests use separate processes, randomized temporary `CODEX_HOME` directories, and ephemeral ports. The remaining shared constraints are system resources; each app-server also uses a multi-thread Tokio runtime, and fuzzy-search tests can create additional worker threads, so the local cap remains intentionally conservative. ## Performance and validation All measurements below are warm, execution-only app-server runs with nextest retries disabled. On the current rebased branch, an AMD EPYC 7763 machine with 16 logical CPUs and 62 GiB RAM completed three consecutive runs: | Run | Nextest time | Wall time | Result | | --- | ---: | ---: | --- | | 1 | 142.941s | 145.17s | 836/836 passed | | 2 | 143.402s | 145.59s | 836/836 passed | | 3 | 142.870s | 145.08s | 836/836 passed | The mean wall time was 145.28s. The slow-inventory, approval replay, and zsh-fork tests all passed with their original deadlines. Earlier measurements on the same Linux machine, before the suite grew, showed the scaling that motivated the change: | App-server concurrency | Nextest time | Result | | --- | ---: | --- | | 1 | 369.5s | 572/572 passed | | 2 | 194.5s | 572/572 passed | | 4 | 111.0s mean over 3 runs | 3/3 clean | Four workers reduced that execution time by about 70%, a roughly 3.3x speedup over serialization.
Adam Perry @ OpenAI ·
2026-06-09 12:48:04 -07:00 -
[codex-analytics] add extensible feature thread sources (#27063)
## Why - `ThreadSource` currently defines a closed set of core-owned values - Product features also create threads for background or scheduled work - Adding every product-specific value to the core enum would require repeated `codex-rs` protocol changes - Feature-backed values let product callers provide precise attribution while preserving the existing core classifications ## What Changed - Adds `ThreadSource::Feature(String)` for app-owned thread source values - Represents all app-server v2 thread sources as scalar strings, so a feature source is supplied as `"automation"` - Persists and emits the feature's plain string label, so `"automation"` produces `thread_source="automation"` in analytics - Keeps `user`, `subagent`, and `memory_consolidation` as explicit core-owned values and regenerates the app-server schemas and TypeScript bindings ## Verification - `just write-app-server-schema` - `cargo check --workspace` - `just test -p codex-protocol feature_thread_source_serializes_as_its_app_owned_label` - `just test -p codex-app-server-protocol thread_sources_round_trip_as_scalar_labels` - `cargo test -p codex-analytics thread_initialized_event_serializes_expected_shape` - `just fmt`
marksteinbrick-oai ·
2026-06-09 12:27:10 -07:00 -
[codex] Test extension API contracts (#26835)
## Why `codex-extension-api` defines contracts shared by extension crates and their hosts, but it had no direct test suite. Host and feature tests cover downstream behavior, while regressions in the API crate's own typed state, registry ordering, and capability adapters could go unnoticed. ## What - Add public-surface integration tests for `ExtensionData`, including concurrent initialization and poison recovery. - Cover contributor registration order, approval short-circuiting, event sink retention, no-op response injection, and closure-based agent spawning. - Add the test-only dependencies used by the suite. ## Validation - `just test -p codex-extension-api` - `just argument-comment-lint -p codex-extension-api` - `just bazel-lock-check`
Adam Perry @ OpenAI ·
2026-06-09 18:30:24 +00:00 -
Load selected executor skills through extensions (#27184)
## Why CCA is moving toward a split runtime where the orchestrator may not have a filesystem, while executors can expose preinstalled plugins and skills. A thread therefore needs to select capabilities without asking app-server or core to interpret executor-owned paths through the orchestrator's filesystem. The longer-term model is broader than executor skills: - A plugin is a bundle of skills, MCP servers, connectors/apps, and hooks. - A plugin root can be local, executor-owned, or hosted by a backend. - Components inside one plugin can use different access and execution mechanisms. A skill may be read from a filesystem or through backend tools; an HTTP MCP server can run without an executor; a stdio MCP server or hook needs an execution environment. - Core should carry generic extension initialization data. The extension that owns a component should discover it, expose it to the model, and invoke it through the appropriate runtime. This PR establishes that architecture through one complete vertical: selecting a root on an executor, discovering the skills beneath it, exposing those skills to the model, and reading an explicitly invoked `SKILL.md` through the same executor. ## Contract `thread/start` gains an experimental `selectedCapabilityRoots` field: ```json { "selectedCapabilityRoots": [ { "id": "deploy-plugin@1", "location": { "type": "environment", "environmentId": "workspace", "path": "/opt/codex/plugins/deploy" } } ] } ``` The root is intentionally not classified as a "plugin" or "skill" in the API. It can point at a standalone skill, a directory containing several skills, or a plugin containing skills and other components. This PR only teaches the skills extension how to consume it; later extensions can resolve MCP, connector, and hook components from the same selection. The platform-supplied `id` is stable selection identity. The location says which runtime owns the root and gives that runtime an opaque path. App-server does not inspect or canonicalize the path. ## What changed ### Generic thread extension initialization App-server converts selected roots into `ExtensionDataInit`. Core carries that generic initialization value until the final thread ID is known, then creates thread-scoped `ExtensionData` before lifecycle contributors run. This keeps `Session` and core independent of the capability-selection contract. The initialization value is consumed during construction; it is not retained as another long-lived `Session` field. ### Executor-backed skills The skills extension now owns an `ExecutorSkillProvider` that: - resolves the selected environment through `EnvironmentManager` - discovers, canonicalizes, and reads skills through that environment's `ExecutorFileSystem` - contributes the bounded selected-skill catalog as stable developer context - reads an explicitly invoked skill body through the authority that listed it - warns when an environment or root is unavailable - never falls back to the orchestrator filesystem for an executor-owned root Skill catalog and instruction fragments have hard byte bounds, which also bound them below the 10K-token per-item context limit. If a selected executor skill has the same name as a legacy local skill, the executor selection owns that invocation and the local body is not injected a second time. Existing local and bundled skill loading remains in place. Omitting `selectedCapabilityRoots` therefore preserves current local-only behavior. ## Current semantics - Only environment-owned locations are represented in this first contract. - Roots are resolved by the destination extension, not by app-server or core. - An unavailable executor or invalid root produces a warning and no capabilities from that root; it does not trigger a local-filesystem fallback. - Selection applies to a newly started active thread. - MCP servers, connectors, and hooks beneath a selected plugin root are not activated yet. - Selection is not yet persisted or inherited across resume, fork, or subagent creation. Existing local capabilities continue to behave as they do today in those flows. ## Planned vertical follow-ups 1. **Hosted HTTP MCP:** add an extension-backed HTTP MCP source that works without an executor, then replace the special-purpose MCP plugins loader with that implementation. 2. **Executor MCP:** register and execute stdio MCP servers through the environment that owns the selected plugin root. 3. **Backend skills:** add a hosted skill source whose catalog and bodies are accessed through extension tools rather than a filesystem. 4. **Connectors and hooks:** activate those components through their owning extensions, using the same selected-root boundary and component-specific runtime. 5. **Durable selection:** define the desired-selection lifecycle, persist it, and make resume, fork, and subagent inheritance explicit rather than accidental. 6. **Local convergence:** incrementally route existing local plugin, skill, and MCP loading through the same extension model while preserving current local behavior. Each follow-up remains reviewable as an end-to-end capability. The platform selects roots, generic thread extension data carries the selection, and the owning extension resolves and operates its component. ## Verification Coverage added for: - app-server end-to-end discovery and explicit invocation of a skill inside an executor-selected plugin root - exclusive invocation when a selected executor skill collides with a local skill name - executor filesystem authority for discovery, canonicalization, and reads - thread extension initialization before lifecycle contributors run - stable executor catalog context, explicit invocation, context rebuilding, hidden skills, and preserved host/remote catalog behavior Targeted protocol, core-skills, skills-extension, core lifecycle, and app-server executor-skill tests were run during development.jif ·
2026-06-09 19:51:54 +02:00 -
app-server: reject direct input to multi-agent v2 sub-agents (#27173)
## Why Multi-agent v2 sub-agents are owned and coordinated by their parent agent. Allowing an app-server client to start or steer turns on a spawned child bypasses the multi-agent messaging path and creates a second, conflicting source of work for that sub-agent. ## What changed - Reject direct `turn/start` and `turn/steer` requests targeting multi-agent v2 thread-spawn sub-agents. - Identify these targets using both the thread's resolved multi-agent version and its `SubAgentSource::ThreadSpawn` session source, leaving root threads, v1 agents, and other sub-agent types unchanged. - Return a consistent invalid-request error before validating or applying the submitted input. ## Testing - Added an app-server integration test that spawns a real multi-agent v2 child and verifies that direct `turn/start` and `turn/steer` requests are rejected.
jif ·
2026-06-09 19:40:40 +02:00 -
fix: Prevent /review crash when entering Esc on steer message (#22879)
This changes the `/review` escape path so `Esc` no longer behaves like the normal queued-follow-up interrupt flow while a review is running. Steering is not currently supported in `/review` mode, without this change users are able to attempt a steer but it leads to a crash (see #22815). If the user has already tried to send additional guidance during `/review`, the TUI now keeps the review running and shows a warning that steer messages are not supported in that mode, while still pointing users to `Ctrl+C` if they actually want to cancel. It also adds regression coverage for the review-specific warning behavior. When users do cancel with Ctrl+C during /review, the TUI now tolerates the active-turn race that can happen during review handoff, and any queued steer messages are restored to the composer instead of being discarded. - Special-case `Esc` during an active `/review` when follow-up steer input is pending or has already been deferred. - Show a clear warning instead of interrupting the running review. - Make the Ctrl+C cancel path during /review resilient to active-turn races, while preserving any queued steer text by restoring it to the composer. - Add review-mode test coverage for the warning path. ## Testing 1. Start a `/review` with a diff large enough that the review stays active for more than a few seconds. 2. While the review is still running, type a follow-up / steer message, submit it, and then press `Esc`. Before: `Esc` causes the TUI to close abruptly. After: the review keeps running and the transcript shows a warning that steer messages are not supported during `/review`, with guidance to use `Ctrl+C` if you want to cancel. 3. Press `Ctrl+C` if you actually want to stop the review. Before: (after restarting the test since Pt. 2 crashed) this is the intentional cancellation path. After: this remains the intentional cancellation path, and any queued follow-up steer text is restored to the composer instead of being lost. ## Note: `/review` mode explicitly does not support steering at this time (as noted in `turn_processer.rs`, if we want to explore that in the future this code will need to be modified). This change keeps unsupported steer attempts from crashing the TUI and preserves queued follow-up text if the user cancels with Ctrl+C.
canvrno-oai ·
2026-06-09 09:41:58 -07:00 -
Avoid rereading rollout history during cold resume (#27031)
## Summary - reuse the history-bearing `StoredThread` loaded while probing for a running thread - avoid rereading and reparsing the rollout when that probe finds no active process - reload after shutting down a loaded thread because shutdown may flush newer rollout items - add a regression test that verifies cold resume performs one history-bearing store read ## Problem `thread/resume` first reads the persisted thread with history while checking whether the thread is already running. When no running process exists, cold resume currently falls through to `resume_thread_from_rollout`, which reads and parses the same history again. That duplicate work grows with rollout size and remains on the synchronous resume path even when the caller requests `excludeTurns`. ## Background The duplicate read was introduced by #24528, which fixed resume overrides for idle cached threads. To support resumes specified by rollout path, `resume_running_thread` began loading the stored thread with history so it could resolve the canonical thread ID and determine whether a cached `CodexThread` was already loaded. That history is needed when the loaded-thread path handles the request. On a cold miss, however, the function's boolean result could only report that no loaded thread handled the request. It discarded the history-bearing `StoredThread`, and the normal cold-resume path immediately loaded and parsed the same rollout again. This change preserves the idle cached-thread behavior from #24528 while allowing the cold-resume path to reuse the probe result. ## Performance I benchmarked real retained rollouts using isolated `CODEX_HOME` directories, explicit rollout paths, debug builds of the commit and its exact parent, and alternating parent/patch order. The table below uses `thread/resume` with `excludeTurns: true`; response payload sizes were identical. | Rollout size | Records | Parent median | Patch median | Median paired saving | | ---: | ---: | ---: | ---: | ---: | | 6 MB | 3,574 | 541 ms | 441 ms | 132 ms | | 30 MB | 15,220 | 1.505 s | 1.041 s | 701 ms | | 60 MB | 31,453 | 2.644 s | 1.742 s | 970 ms | | 149 MB | 100,874 | 10.506 s | 7.156 s | 3.350 s | | 559 MB | 259,734 | 27.759 s | 16.725 s | 9.836 s | The absolute saving increases with thread size, as expected when removing one complete JSONL history read and parse. Total resume time is also content-dependent, so the relationship is not perfectly linear. I also tested full-history resume with `excludeTurns: false`. The response payload was byte-identical between variants, and the same size-dependent improvement remained visible: | Rollout size | Parent median | Patch median | Median paired saving | | ---: | ---: | ---: | ---: | | 6 MB | 1.052 s | 904 ms | 270 ms | | 30 MB | 2.667 s | 1.762 s | 924 ms | | 60 MB | 8.464 s | 6.272 s | 3.680 s | | 149 MB | 26.719 s | 12.118 s | 14.601 s | | 559 MB | 40.359 s | 25.475 s | 16.590 s | ## Validation - `just test -p codex-app-server cold_thread_resume_reuses_non_local_history_probe` - `just fix -p codex-app-server -p codex-thread-store` - `just fmt`
Zanie Blue ·
2026-06-09 11:16:27 -05:00