Commit Graph

6750 Commits

  • [codex] Add rollout-backed thread content search (#23519)
    ## Summary
    - add experimental `thread/search` for local rollout-backed thread
    search using `rg` over JSONL rollouts
    - return search-specific result rows with optional previews instead of
    storing preview data on `StoredThread` or ordinary `Thread` responses
    - keep `thread/list` separate from full-content search and document the
    new app-server surface
    
    ## Testing
    - `cargo test -p codex-app-server-protocol`
    - `cargo test -p codex-app-server
    thread_search_returns_content_and_title_matches -- --nocapture`
  • TUI: skip goal replace prompt for completed goals (#23792)
    ## Why
    Users reported that the replacement confirmation feels unnecessary when
    the current thread goal is already complete. In that state, `/goal
    <objective>` is starting fresh rather than interrupting active work.
    
    ## What changed
    `/goal <objective>` now skips the replace confirmation when the existing
    goal has `complete` status and uses the existing fresh replacement path.
    Goals that are active, paused, blocked, usage-limited, or budget-limited
    still require confirmation before being replaced.
  • Reconnect disconnected exec-server websocket clients with fresh sessions (#23867)
    ## Summary
    - replace the one-shot lazy remote exec-server cache with a
    lock-protected current client
    - when the cached websocket client is already disconnected, create one
    fresh websocket client/session on the next `get()`
    - keep existing disconnect failure behavior for old process sessions and
    HTTP body streams; do not add session resume or request retry
    
    ## Why
    The prior PR direction was trying to grow into session restore: resume
    the old `session_id`, preserve existing process handles, and add
    reconnect retry policy. That is more machinery than we want for this
    slice.
    
    For now, the useful minimum is simpler: later fresh remote operations
    should not be stuck behind a dead cached websocket client, but anything
    already attached to the dead connection should fail loudly through the
    existing disconnect path. The server already has detached-session
    cleanup via its existing TTL, so this PR does not need to add
    client-side session preservation.
    
    ## What Changed
    - `LazyRemoteExecServerClient::get()` now keeps the current concrete
    client in a small mutex-protected cache plus one async connect lock.
    - If that cached client is still connected, `get()` returns it.
    - If that cached websocket client has observed the transport close,
    `get()` creates a brand-new websocket client with a brand-new
    exec-server session and replaces the cache.
    - If that cached client is stdio-backed, behavior stays one-shot: the
    dead client is returned and later work surfaces the existing disconnect
    error.
    - No `resume_session_id`, backoff, request replay, or existing
    `RemoteExecProcess` rebinding is added here.
    - Added focused websocket coverage that proves two concurrent `get()`
    calls after disconnect share one fresh replacement client/session.
  • Improve /goal error messages for ephemeral sessions (#23796)
    ## Why
    
    When a user runs `/goal` in a temporary session, the TUI can currently
    surface an internal app-server failure such as `thread/goal/get failed
    in TUI`. That message is technically true, but it does not explain the
    actual constraint: goals require a saved session because goal state is
    persisted with the thread.
    
    This is especially confusing when `codex doctor` reports the background
    app-server as running in ephemeral mode, since that wording is easy to
    conflate with ephemeral thread/session behavior.
    
    ## What changed
    
    - Added a TUI-side formatter for thread-goal RPC failures in
    `codex-rs/tui/src/app/thread_goal_actions.rs`.
    - Detects app-server/core errors that indicate goals are unsupported for
    an ephemeral thread/session.
    - Replaces the internal RPC failure with a user-facing explanation:
    
    ```text
    Goals need a saved session. This session is temporary.
    Run `codex` to start a saved session, or `codex resume` / `/resume` to reopen one.
    ```
    
    - Preserves the existing generic failure wording for non-ephemeral goal
    errors.
    
    ## Verification
    
    - `cargo test -p codex-tui thread_goal_error_message --lib`
    
    I also tried `cargo test -p codex-tui`; it built successfully but the
    test runner aborted in an unrelated side-thread stack overflow
    (`app::tests::discard_side_thread_removes_agent_navigation_entry`),
    which reproduced when run by itself.
  • packaging: move rg manifest out of npm bin (#23833)
    ## Why
    
    Installing `@openai/codex` currently places a Dotslash `rg` manifest at
    `node_modules/@openai/codex/bin/rg`, even though the native optional
    dependency already ships the actual helper under
    `vendor/<target>/codex-path/rg`. The launcher prepends that `codex-path`
    directory, so the top-level `bin/rg` file is redundant in the npm
    install.
    
    The remaining direct consumers of the manifest are package-building
    paths: `scripts/codex_package/ripgrep.py` and
    `codex-cli/scripts/install_native_deps.py`. Keeping the manifest under
    `codex-cli/bin` makes it look like a shipped npm binary, so this moves
    it next to the package-builder code that owns it. The checked-in
    `@openai/codex` package metadata should likewise describe only the meta
    package payload; generated platform packages continue to publish
    `vendor`.
    
    ## What Changed
    
    - Moved the Dotslash ripgrep manifest from `codex-cli/bin/rg` to
    `scripts/codex_package/rg`.
    - Updated the package builder, npm native-artifact hydrator, README, and
    CLI help text to reference the new manifest location.
    - Stopped `codex-cli/scripts/build_npm_package.py` from copying `rg`
    into the `@openai/codex` meta package.
    - Narrowed the checked-in meta package `files` whitelist to
    `bin/codex.js`.
    
    ## Verification
    
    - `python3 -m unittest discover -s scripts/codex_package -p "test_*.py"`
    - `python3 -m unittest discover -s codex-cli/scripts -p "test_*.py"`
    - `python3 -m py_compile codex-cli/scripts/build_npm_package.py
    codex-cli/scripts/install_native_deps.py
    scripts/codex_package/ripgrep.py scripts/codex_package/cli.py
    scripts/stage_npm_packages.py`
    - `codex-cli/scripts/build_npm_package.py --package codex --version
    0.0.0-test --pack-output <tmp>/codex-meta-no-vendor.tgz`
    - `tar -tf <tmp>/codex-meta-no-vendor.tgz` showed only
    `package/bin/codex.js`, `package/package.json`, and `package/README.md`.
    - Direct staging check showed `codex` uses `files: ["bin/codex.js"]`
    while `codex-darwin-arm64` still uses `files: ["vendor"]`.
    
    ---
    [//]: # (BEGIN SAPLING FOOTER)
    Stack created with [Sapling](https://sapling-scm.com). Best reviewed
    with [ReviewStack](https://reviewstack.dev/openai/codex/pull/23833).
    * #23836
    * __->__ #23833
  • tui: plumb permission profile selection (#23708)
    ## Why
    
    The named-profile `/permissions` picker needs a small TUI action path
    that can select permission profiles without folding the menu UI and
    profile metadata into the same review.
    
    ## What changed
    
    - Carry permission-profile selections through the TUI app event flow.
    - Persist selected profiles while preserving the existing approval
    settings and guardrail prompts.
    - Keep the legacy `/permissions` picker behavior in this layer; the
    profile-mode menu stays in the follow-up PR.
    
    ## Stack
    
    1. [#22931](https://github.com/openai/codex/pull/22931):
    runtime/session/network propagation for active permission profiles.
    2. **This PR**: TUI selection plumbing and guardrail flow.
    3. [#21559](https://github.com/openai/codex/pull/21559): profile-aware
    `/permissions` menu and custom profile display.
    
    <img width="1632" height="1186" alt="image"
    src="https://github.com/user-attachments/assets/69ddcd5e-b57c-468d-8c1d-246916323c15"
    />
    
    ## Validation
    
    - `git diff --cached --check` before commit.
    - Full test run skipped at the user request while pushing the split
    stack.
  • cli: remove legacy profile v1 plumbing (#23886)
    ## Why
    
    [#23883](https://github.com/openai/codex/pull/23883) moved the
    user-facing `--profile` flag onto profile v2. The shared CLI option
    layer still carried the old `config_profile` slot and several CLI
    entrypoints still copied that value into legacy config overrides.
    Leaving that path around makes the CLI surface look like it still
    selects legacy `[profiles.*]` state even though `--profile` now means
    `$CODEX_HOME/<name>.config.toml`.
    
    ## What
    
    - Remove the legacy `config_profile` field and merge/copy path from
    [`SharedCliOptions`](https://github.com/openai/codex/blob/95baaf72920c8db22097df8d15a0bb76c84528b6/codex-rs/utils/cli/src/shared_options.rs#L8-L177).
    - Stop forwarding profile-v1 overrides from CLI, exec, TUI, doctor,
    debug, feature, and exec-server paths; runtime profile selection remains
    on `config_profile_v2` through
    [`loader_overrides_for_profile`](https://github.com/openai/codex/blob/95baaf72920c8db22097df8d15a0bb76c84528b6/codex-rs/cli/src/main.rs#L1606-L1619).
    - Resolve local OSS provider selection from the base config in exec and
    TUI now that the legacy profile argument is gone.
    
    ## Testing
    
    - Not run (cleanup-only follow-up to #23883).
  • Route MCP servers through explicit environments (#23583)
    ## Summary
    - route each configured MCP server through an explicit per-server
    `environment_id` instead of a manager-wide remote toggle
    - default omitted `environment_id` to `local`, resolve named ids through
    `EnvironmentManager`, and fail only the affected MCP server when an
    explicit id is unknown
    - keep local stdio on the existing local launcher path for now, while
    named-environment stdio uses the selected environment backend and
    requires an absolute `cwd`
    - allow local HTTP MCP servers to keep using the ambient HTTP client
    when no local `Environment` is configured; named-environment HTTP MCPs
    use that environment's HTTP client
    
    ## Validation
    - devbox Bazel build: `bazel build --bes_backend= --bes_results_url=
    //codex-rs/cli:codex //codex-rs/rmcp-client:test_stdio_server
    //codex-rs/rmcp-client:test_streamable_http_server`
    - devbox app-server config matrix with real `config.toml` /
    `environments.toml` files covering omitted local, explicit local,
    omitted local under remote default, explicit remote stdio, local HTTP
    without local env, explicit remote HTTP, local stdio without local env,
    unknown explicit env, and remote stdio without `cwd`
  • docs: add description to codex-cli/package.json (#23835)
    Fix this eyesore where our lack of a `"description"` was causing our
    `README.md` to be used for previews on npm.
    
    <img width="1291" height="178" alt="image"
    src="https://github.com/user-attachments/assets/a9bc08c5-0def-4755-8bcc-0c90e096b9c2"
    />
  • cli: rename profile v2 flag to --profile (#23883)
    ## Why
    
    Profile v2 is taking over the user-facing profile selection path, so the
    CLI no longer needs to expose the transitional `--profile-v2` spelling.
    This switches the public args surface to `--profile` before the
    remaining legacy profile plumbing is removed separately.
    
    ## What
    
    - Rebind `--profile` and `-p` to the v2 profile name argument that
    selects `$CODEX_HOME/<name>.config.toml`.
    - Stop parsing the legacy shared CLI profile argument while keeping its
    implementation path in place for follow-up cleanup.
    - Update CLI validation, profile-name parse errors, and the
    legacy-profile collision message/tests to refer to `--profile`.
    
    ## Testing
    
    - `cargo test -p codex-cli -p codex-config -p codex-protocol -p
    codex-utils-cli`
  • chore: link doc in profile error messages (#23879)
    Just updating the error message with a link to the doc
  • refactor: centralize tool exposure planning (#23876)
    ## Why
    
    Tool exposure is a planning concern, but the deferred MCP path and
    dispatch-only legacy shell path were carrying those decisions in handler
    constructors and a shell-only tool-family builder. Keeping those
    decisions in `spec_plan` makes the core tool plan easier to follow and
    keeps handlers focused on runtime behavior.
    
    ## What changed
    
    - add `PlannedTools` helpers for ordinary runtimes, exposure overrides,
    dispatch-only runtimes, and hosted specs
    - inline shell tool assembly into `core/src/tools/spec_plan.rs` and
    remove the shell-only `tool_family` module
    - remove exposure state and special exposure constructors from
    `McpHandler` and `ShellCommandHandler`
    - keep hidden runtime behavior centralized in `ExposureOverride`,
    including disabling parallel tool calls for hidden handlers
    
    ## Testing
    
    - Not run (refactor only)
  • [codex] Stabilize subagent start hook test (#23882)
    ## What
    
    Remove the exact captured request-count assertion from the
    `SubagentStart` hook integration test while still waiting for the child
    request that matches the injected hook context.
    
    ## Why
    
    The test owns the start-hook behavior and already verifies that the
    child request reaches the context matcher plus that the start/session
    hook logs have the expected invocations. Counting every request captured
    by the response mock makes the test sensitive to lifecycle timing
    outside that contract and has been flaky in CI.
    
    ## Testing
    
    - `cargo test -p codex-core --test all
    suite::subagent_notifications::subagent_start_replaces_session_start_and_injects_context
    -- --exact`
  • Make tool executor specs mandatory (#23870)
    ## Why
    
    `ToolExecutor` is the runtime contract that keeps a callable tool and
    its model-visible spec together. Leaving `spec()` optional lets a
    registered runtime silently omit that half of the contract, and it also
    overloads a missing spec as an exposure decision for tools that should
    stay dispatchable without being shown to the model.
    
    ## What
    
    - Make `ToolExecutor::spec()` required and update core, extension, and
    test tool executors to return a concrete `ToolSpec`.
    - Add `ToolExposure::Hidden` for dispatch-only tools. The legacy
    `shell_command` runtime in unified-exec sessions now uses that explicit
    exposure instead of hiding itself by omitting a spec.
    - Build MCP tool specs when `McpHandler` is constructed so invalid MCP
    specs are skipped before the handler is registered.
    - Keep tool planning aligned with the new contract for direct, deferred,
    hidden, code-mode, dynamic, and namespaced tool paths.
    
    ## Testing
    
    - Added tool-plan coverage that invalid MCP tool specs are not
    registered.
    - Updated shell-family coverage for the hidden legacy `shell_command`
    runtime and the affected tool executor test fixtures.
  • feat: retain remote compaction truncation parity in v2 (#23728)
    ## Why
    
    Remote compaction now has two implementations: the existing
    server-rebuilt v1 path and the newer client-rebuilt v2 path behind
    `remote_compaction_v2`. The v1 path bounds retained
    user/developer/system history before installing the compaction item,
    while v2 was previously carrying the full retained history forward. That
    made the two paths diverge for large pre-compaction transcripts even
    though they are meant to preserve the same compaction contract.
    
    This aligns v2 with the retained-history budget expected from v1 so
    switching the feature flag does not materially change which
    pre-compaction messages survive into the rebuilt history.
    
    ## What changed
    
    - Apply a retained-message character budget while rebuilding v2
    compacted history in `core/src/compact_remote_v2.rs`.
    - Keep newest retained messages first, truncate the boundary message
    with the shared `truncate_text(...)` helper, and drop older retained
    messages once the budget is exhausted.
    - Preserve non-text retained message content such as images while
    truncating text content.
    - Use the current `64_000` token retained-message default translated to
    the existing `4x` character budget.
    
    ## Testing
    
    - `cargo test -p codex-core compact_remote_v2::tests::`
    - Added focused coverage for newest-first retention and truncating
    multipart retained messages without dropping images.
  • [codex] Steer budget-limited goal extension turns (#23718)
    ## What
    - Add a small extension capability for injecting model-visible response
    items into the active turn
    - Have the goal extension inject hidden goal-context steering when
    tool-finish accounting reaches `BudgetLimited`
    - Cover the extension backend path with an assertion on the injected
    steering item
    
    ## Why
    PR #23696 persists and emits the budget-limited goal update from
    tool-finish accounting, but it leaves the model unaware of that
    transition. The existing core runtime steers the model to wrap up in
    this case; the extension path should do the same through an explicit
    host capability.
    
    ## Testing
    - `just fmt`
    - `cargo test -p codex-goal-extension`
    - `cargo test -p codex-extension-api`
  • Trace logical websocket request after untraced warmup (#23581)
    ## Why
    
    `prewarm_websocket` intentionally stays out of rollout inference
    tracing, but the next traced websocket request can still reuse the
    warmup `response_id` and send an empty `input` delta. If tracing records
    that wire payload verbatim, replay sees an incremental request whose
    parent was never traced and cannot reconstruct the conversation.
    
    This fixes that at the producer boundary instead of relaxing
    `rollout-trace` replay semantics around unresolved
    `previous_response_id` values.
    
    ## What
    
    - track whether the last websocket response came from an untraced warmup
    and clear that state when the websocket session is reset or reconnected
    - when a traced websocket request reuses that warmup parent, keep
    sending the compressed websocket request on the wire but record the
    logical `ResponsesApiRequest` in the rollout trace
    - add a regression test that proves replay reconstructs the logical user
    message even though the websocket follow-up carries
    `previous_response_id = warm-1` with empty `input`
    - update `InferenceTraceAttempt::record_started` docs to reflect that
    callers may record a logical request rather than the exact transport
    payload
    
    ## Testing
    
    - `cargo test -p codex-core --test all
    responses_websocket_request_prewarm_traces_logical_request`
  • sdk: launch packaged Codex runtimes (#23786)
    ## Why
    
    The Python and TypeScript SDKs launch the native Codex runtime directly,
    so they need to consume the same package artifact shape that release
    jobs now produce. The runtime wheel should be built from the canonical
    Codex package archive rather than reconstructing a parallel layout from
    loose binaries.
    
    ## What Changed
    
    - Stage `openai-codex-cli-bin` by extracting
    `codex-package-<target>.tar.gz` into `src/codex_cli_bin` and validating
    the expected package layout.
    - Update release workflows to pass the generated package archive into
    `stage-runtime` instead of the temporary package directory.
    - Update Python runtime setup to download `codex-package-*.tar.gz`
    release assets directly.
    - Expose Python runtime helpers for the bundled package directory and
    `codex-path`, and prepend that path when `openai_codex` launches the
    installed runtime without duplicating Windows `Path`/`PATH` keys.
    - Teach the TypeScript SDK to resolve package-layout optional
    dependencies while keeping the existing npm fallback layout, and
    preserve the existing Windows path variable casing when prepending
    `codex-path`.
    
    ## Test Plan
    
    - `python3 -m py_compile sdk/python/scripts/update_sdk_artifacts.py
    sdk/python/_runtime_setup.py sdk/python/src/openai_codex/client.py
    sdk/python-runtime/src/codex_cli_bin/__init__.py`
    - `uv run --frozen --project sdk/python --extra dev ruff check
    sdk/python/scripts/update_sdk_artifacts.py sdk/python/_runtime_setup.py
    sdk/python/src/openai_codex/client.py
    sdk/python/tests/test_artifact_workflow_and_binaries.py
    sdk/python-runtime/src/codex_cli_bin/__init__.py`
    - `uv run --frozen --project sdk/python --extra dev pytest
    sdk/python/tests/test_artifact_workflow_and_binaries.py`
    - `pnpm eslint src/exec.ts tests/exec.test.ts`
    - `pnpm test --runInBand tests/exec.test.ts`
  • core: pass permission profiles to Windows runner (#23715)
    ## Why
    
    This is the functional handoff PR for the Windows sandbox
    `PermissionProfile` migration. After #23714, the Windows elevated
    backend can accept a profile-native request, but core still sent a
    compatibility `SandboxPolicy` into the elevated command-runner path.
    That meant profile-only details such as deny globs had to be translated
    through side channels instead of being preserved in the runner
    `SpawnRequest`.
    
    Passing the real `PermissionProfile` completes the command-runner
    handoff while leaving the unelevated restricted-token fallback on the
    legacy policy-string API.
    
    ## What
    
    - Updates one-shot Windows elevated execution in `core/src/exec.rs` to
    call `run_windows_sandbox_capture_for_permission_profile_elevated`.
    - Updates unified exec in `core/src/unified_exec/process_manager.rs` to
    call `spawn_windows_sandbox_session_elevated_for_permission_profile`.
    - Passes `request.permission_profile` /
    `exec_request.permission_profile` and the stored Windows sandbox policy
    cwd to the elevated backend.
    - Keeps compatibility `SandboxPolicy` serialization only for the
    non-elevated restricted-token fallback.
    
    ## Verification
    
    - `cargo test -p codex-core --test all --no-run`
  • feat: support managed permission profiles in requirements.toml (#23433)
    ## Why
    
    Cloud-managed `requirements.toml` should be able to define the managed
    permission profiles a client may select and constrain that selectable
    set without requiring local user config to recreate the profile catalog.
    
    This keeps requirements focused on restrictions. The selected default
    remains a config or session choice, while requirements contribute the
    managed profile bodies and `allowed_permissions` allowlist that the
    config-loading boundary validates before a resolved runtime
    `PermissionProfile` is installed.
    
    ## What changed
    
    - Add `requirements.toml` support for a managed permission-profile
    catalog plus its allowlist:
    
    ```toml
    allowed_permissions = ["review", "build"]
    
    [permissions.review]
    extends = ":read-only"
    
    [permissions.build]
    extends = ":workspace"
    ```
    
    - Merge requirements-defined profile bodies into the effective
    permission catalog and reject profile ids that collide with
    config-defined profiles.
    - Validate that every `allowed_permissions` entry resolves to a built-in
    or catalog profile before selection uses it.
    - Preserve allowed configured named-profile selections. When a
    configured named profile is disallowed, fall back to the first allowed
    requirements profile with a startup warning.
    - Keep built-in selections and the stock trust-based `:read-only` /
    `:workspace` fallback path intact when no permission profile is
    explicitly selected.
    - Centralize the managed catalog and allowlist selection path in
    `EffectivePermissionSelection` so the requirements boundary is visible
    in config loading.
    - Surface `allowedPermissions` through `configRequirements/read`, and
    update the generated app-server schema fixtures plus the app-server
    README.
    
    ## Validation
    
    - `cargo test -p codex-config`
    - `cargo test -p codex-core system_requirements_`
    - `cargo test -p codex-core system_allowed_permissions_`
    - `cargo test -p codex-app-server-protocol`
    - `just write-app-server-schema`
    
    ## Related work
    
    - Uses merged permission-profile inheritance support from #22270 and
    #23705.
    - Kept separate from the in-flight permission profile listing API in
    #23412.
  • windows-sandbox: add profile-native elevated APIs (#23714)
    ## Why
    
    This is the next step after #23167 in the Windows sandbox
    `PermissionProfile` migration. The elevated Windows backend still
    exposed policy-string entry points, which forced callers to pass a
    compatibility `SandboxPolicy` before the command-runner IPC could
    receive a profile.
    
    Adding profile-native APIs first keeps the core switch in the next PR
    small: reviewers can see that the Windows crate can prepare elevated
    setup, capability SIDs, and runner IPC from a resolved
    `PermissionProfile` without changing core behavior yet.
    
    ## What
    
    - Adds `ElevatedSandboxProfileCaptureRequest` and
    `run_windows_sandbox_capture_for_permission_profile_elevated` for
    one-shot elevated capture.
    - Adds `spawn_windows_sandbox_session_elevated_for_permission_profile`
    for unified exec sessions.
    - Factors elevated spawn prep through
    `prepare_elevated_spawn_context_for_permissions`, so both new APIs
    operate from `ResolvedWindowsSandboxPermissions` directly.
    - Keeps the existing legacy policy-string APIs as adapters for callers
    that have not moved yet.
    
    ## Verification
    
    - `cargo test -p codex-windows-sandbox`
    
    
    
    
    
    
    
    
    
    
    
    
    ---
    [//]: # (BEGIN SAPLING FOOTER)
    Stack created with [Sapling](https://sapling-scm.com). Best reviewed
    with [ReviewStack](https://reviewstack.dev/openai/codex/pull/23714).
    * #23715
    * __->__ #23714
  • [codex] Reject read-only fallback with approvals disabled (#23774)
    ## Why
    
    If a user configures `approval_policy = "never"` with `sandbox_mode =
    "danger-full-access"`, managed requirements can reject full access and
    force the existing permission fallback to read-only. That leaves Codex
    in a dead-end session: writes are blocked by the sandbox, while
    approvals are disabled so the session cannot ask to proceed.
    
    This PR rejects that constrained configuration during startup instead of
    letting the TUI enter a read-only session that cannot make progress. The
    rejection is attached to the requirement-constrained permission path in
    [`Config`](https://github.com/openai/codex/blob/39f0abc0a7c0ed0e348a6843e9f0c7b76e2400bc/codex-rs/core/src/config/mod.rs#L3301-L3318).
    
    ## What changed
    
    - Reject the `danger-full-access` to read-only managed-requirements
    fallback when the effective approval policy is `never`.
    - Explain in the startup config error why the fallback is invalid and
    how to fix it.
    - Add a regression test for the managed requirements path.
  • Use named MITM permissions config (#18240)
    ## Stack
    1. Parent PR: #18868 adds MITM hook config and model only.
    2. Parent PR: #20659 wires hook enforcement into the proxy request path.
    3. This PR changes the user facing PermissionProfile TOML shape.
    
    ## Why
    1. The broader goal is to make MITM clamping usable from the same
    permission profile that already controls network behavior.
    2. This PR is the config UX layer for the stack. It moves MITM policy
    into `[permissions.<profile>.network.mitm]` instead of exposing the flat
    runtime shape to users.
    3. The named hook and action tables belong here because users need
    reusable policy blocks that are easy to review, while the proxy runtime
    only needs a flat hook list.
    4. This PR validates action refs during config parsing so mistakes in
    the user facing policy fail before a proxy session starts.
    5. Keeping the lowering here lets the proxy keep its simpler runtime
    model and lets PermissionProfile remain the single source of network
    permission policy.
    
    ## Summary
    1. Keep MITM policy inside `[permissions.<profile>.network.mitm]` so the
    selected PermissionProfile owns network proxy policy.
    2. Use named MITM hooks under
    `[permissions.<profile>.network.mitm.hooks.<name>]`.
    3. Put host, methods, path prefixes, query, headers, body, and action
    refs on the hook table.
    4. Define reusable action blocks under
    `[permissions.<profile>.network.mitm.actions.<name>]`.
    5. Represent action blocks with `NetworkMitmActionToml`, then lower them
    into the proxy runtime action config.
    6. Reject unknown refs, empty refs, and empty action blocks during
    config parsing.
    7. Keep the runtime hook model unchanged by lowering config into the
    existing proxy hook list.
    8. Preserve the #20659 activation fix for nested MITM policy.
    
    ## Example
    ```toml
    [permissions.workspace.network.mitm]
    enabled = true
    
    [permissions.workspace.network.mitm.hooks.github_write]
    host = "api.github.com"
    methods = ["POST", "PUT"]
    path_prefixes = ["/repos/openai/"]
    action = ["strip_auth"]
    
    [permissions.workspace.network.mitm.actions.strip_auth]
    strip_request_headers = ["authorization"]
    ```
    
    ## Validation
    1. Regenerated the config schema.
    2. Ran the core MITM config parsing and validation tests.
    3. Ran the core PermissionProfile MITM proxy activation tests.
    4. Ran the core config schema fixture test.
    5. Ran the network proxy MITM policy tests.
    6. Ran the scoped Clippy fixer for the network proxy crate.
    7. Ran the scoped Clippy fixer for the core crate.
    
    ---------
    
    Co-authored-by: Winston Howes <winston@openai.com>
  • [codex] Add plugin id to MCP tool call items (#23737)
    Add owning plugin id to MCP tool call items so we can better filter them
    at plugin level.
    
    ## Summary
    - add optional `plugin_id` to MCP tool-call items and legacy begin/end
    events
    - propagate plugin metadata into emitted core items and app-server v2
    `ThreadItem::McpToolCall`
    - preserve plugin ids through app-server replay/redaction paths and
    regenerate v2 schema fixtures
    
    ## Testing
    - `just write-app-server-schema`
    - `just fmt`
    - `just fix -p codex-core`
    - `cargo test -p codex-protocol -p codex-app-server-protocol`
    - `cargo test -p codex-app-server-protocol`
    - `cargo test -p codex-core mcp_tool_call_item_includes_plugin_id --lib`
    - `cargo check -p codex-tui --tests`
    - `cargo check -p codex-app-server --tests`
    - `git diff --check`
    
    ## Notes
    - `just fix -p codex-core` completed with two non-fatal
    `too_many_arguments` warnings on the touched MCP notification helpers.
    - A broader `cargo test -p codex-core` run passed core unit tests, then
    hit shell/sandbox/snapshot failures in the integration target.
    - A broader app-server downstream run hit the existing
    `in_process::tests::in_process_start_clamps_zero_channel_capacity` stack
    overflow; `cargo test -p codex-exec` also hit the existing sandbox
    expectation mismatch in
    `thread_lifecycle_params_include_legacy_sandbox_when_no_active_profile`.
  • ci: run Codex package builder tests (#23760)
    ## Why
    
    #23752 and #23759 add Python unit tests for the Codex package builder,
    but the root CI workflow did not run tests under
    `scripts/codex_package`. That left the `zstd` resolution and
    prebuilt-resource packaging behavior covered locally without a CI check.
    
    ## What changed
    
    - Add a root CI step in `.github/workflows/ci.yml` that runs `python3 -m
    unittest discover -s scripts/codex_package -p "test_*.py"`.
    - Keep the step with the existing Python verification checks before
    Node/pnpm setup.
    
    ## Verification
    
    - `python3 -m unittest discover -s scripts/codex_package -p "test_*.py"`
    - `python3 -m py_compile scripts/codex_package/*.py`
  • Remove Windows sandbox resource stamping (#23764)
    ## Why
    
    The `codex-windows-sandbox` crate was embedding Windows resource
    metadata through a package-level `build.rs`. Because that package also
    exposes the `codex_windows_sandbox` library, downstream binaries that
    link the library could inherit `FileDescription` / `ProductName` values
    of `codex-windows-sandbox`.
    
    That made ordinary Codex binaries, including the long-lived `codex.exe`
    app-server sidecar, appear as `codex-windows-sandbox` in Windows UI
    surfaces such as Task Manager / file properties.
    
    We do not rely on this metadata enough to justify a larger bin-only
    resource split, so this removes the resource stamping entirely.
    
    ## What changed
    
    - Removed the `windows-sandbox-rs` build script that invoked `winres`.
    - Removed the setup manifest that was only consumed by that build
    script.
    - Removed the `winres` build dependency and corresponding `Cargo.lock` /
    `MODULE.bazel.lock` entries.
    - Removed the now-unused Bazel build-script data.
    
    ## Verification
    
    - `cargo build -p codex-windows-sandbox --bins`
    - `cargo build -p codex-cli --bin codex`
    - `bazel mod deps --lockfile_mode=update` via Bazelisk, with local
    remote-cache-disabling flags because `bazel` is not installed on PATH
    here
    - `bazel mod deps --lockfile_mode=error` via Bazelisk, with the same
    local flags
    - Verified rebuilt `codex.exe`, `codex-command-runner.exe`, and
    `codex-windows-sandbox-setup.exe` now have blank `FileDescription` /
    `ProductName` fields.
    - `cargo test -p codex-windows-sandbox` still fails on two legacy
    Windows sandbox tests with `CreateRestrictedToken failed: 87` and the
    follow-on poisoned test lock; 85 passed, 2 ignored.
  • [codex] Fix realtime v1 websocket compatibility (#23771)
    ## Why
    
    Realtime v1 websocket sessions now expect a slightly different boundary
    shape for text input, completed input transcripts, and connection
    headers. Codex was still using the older shape, so some v1 text appends
    could be rejected before the existing conversation flow could handle
    them.
    
    ## What changed
    
    - Send v1 user text items with `input_text` content
    - Accept v1 turn-marked input transcript events as completed transcripts
    - Add the v1 alpha header only for v1 realtime sessions
    - Cover the outbound text shape, transcript parsing, and versioned
    headers
    
    ## Test plan
    
    - `cargo test -p codex-api endpoint::realtime_websocket::methods::tests`
    - `cargo test -p codex-core quicksilver_alpha_header`
  • Honor client-resolved service tier defaults (#23537)
    ## Why
    
    Model catalog responses can now advertise a nullable
    `default_service_tier` for each model. Codex needs to preserve three
    distinct states all the way from config/app-server inputs to inference:
    
    - no explicit service tier, so the client may apply the current model
    catalog default when FastMode is enabled
    - explicit `default`, meaning the user intentionally wants standard
    routing
    - explicit catalog tier ids such as `priority`, `flex`, or future tiers
    
    Keeping those states distinct prevents the UI from showing one tier
    while core sends another, especially after model switches or app-server
    `thread/start` / `turn/start` updates.
    
    ## What Changed
    
    - Plumbed `default_service_tier` through model catalog protocol types,
    app-server model responses, generated schemas, model cache fixtures, and
    provider/model-manager conversions.
    - Added the request-only `default` service tier sentinel and normalized
    legacy config spelling so `fast` in `config.toml` still materializes as
    the runtime/request id `priority`.
    - Moved catalog default resolution to the TUI/client side, including
    recomputing the effective service tier when model/FastMode-dependent
    surfaces change.
    - Updated app-server thread lifecycle config construction so
    `serviceTier: null` preserves explicit standard-routing intent by
    mapping to `default` instead of internal `None`.
    - Kept core responsible for validating explicit tiers against the
    current model and stripping `default` before `/v1/responses`, without
    applying catalog defaults itself.
    
    ## Validation
    
    - `CARGO_INCREMENTAL=0 cargo build -p codex-cli`
    - `CARGO_INCREMENTAL=0 cargo test -p codex-app-server model_list`
    - `cargo test -p codex-tui service_tier`
    - `cargo test -p codex-protocol service_tier_for_request`
    - `cargo test -p codex-core get_service_tier`
    - `RUST_MIN_STACK=8388608 CARGO_INCREMENTAL=0 cargo test -p codex-core
    service_tier`
  • Make goals feature on by default and no longer experimental (#23732)
    ## Why
    
    The `goals` feature is ready to be available without requiring users to
    opt into experimental features. Keeping it behind the beta flag leaves
    persisted thread goals and automatic goal continuation disabled by
    default.
    
    This PR also marks the goal-related app server APIs and events as no
    longer experimental.
    
    ## What changed
    
    - Mark `goals` as `Stage::Stable`.
    - Enable `goals` by default in `codex-rs/features/src/lib.rs`.
  • feat(plugins): tabulate plugin list output (#23727)
    ## Summary
    - render `codex plugin list` as one table per marketplace with the
    marketplace manifest path shown above each table
    - surface the installed plugin version in the CLI output by threading
    `installed_version` through marketplace listing state
    - narrow the system-root exemption so only known bundled/runtime
    marketplaces skip missing-manifest failures, and keep `VERSION` empty
    for cached-but-unconfigured plugins
    
    ## Rationale
    The plugin list UX was hard to scan as a flat list and did not show
    which installed version was active. This change makes the CLI output
    easier to read in the real multi-marketplace case, keeps the plugin path
    visible, fixes the Sapphire regression where bundled/runtime marketplace
    roots were blocking `plugin list`, and addresses the two review findings
    that came out of the follow-up deep review.
    
    ## Key Decisions
    - kept the CLI output grouped per marketplace instead of one global
    table so the marketplace path can live with the rows it owns
    - kept `VERSION` as the installed version, which means it is empty until
    a plugin is actually installed
    - handled the bundled/runtime regression in the CLI snapshot validation
    path rather than widening app-server protocol or changing marketplace
    loading behavior
    - narrowed the exemption to known system marketplace names plus expected
    system paths, so user-configured marketplaces under those directories
    still fail loudly
    - gated `installed_version` on actual installed state so `VERSION`
    cannot show stale cache state for `not installed` rows
    
    ## Validation
    - `just fmt`
    - Sapphire: `cargo test -p codex-cli --test plugin_cli` (`14 passed; 0
    failed`)
    - Sapphire smoke test: bundled/runtime roots still work
      - `cargo run -q -p codex-cli -- plugin add sample@debug`
      - `cargo run -q -p codex-cli -- plugin list`
    - verified the bundled/runtime-root scenario no longer errors and shows
    the expected marketplace table output
    - Sapphire smoke test: custom marketplace under bundled path still
    errors
    - verified `failed to load configured marketplace snapshot(s)` for
    `custom-marketplace`
    - Sapphire smoke test: cached-but-unconfigured plugin hides version
    - verified `sample@debug not installed` renders with an empty `VERSION`
    column
    
    ## Sample Output
    ```text
    /tmp/custom-marketplace/plugin.json
    NAME          VERSION  STATUS         DESCRIPTION
    sample@debug  1.0.0    enabled        Debug sample plugin
    other@local            not installed  Local development plugin
    ```
  • Add SubagentStop hook (#22873)
    # What
    
    <img width="1792" height="1024" alt="image"
    src="https://github.com/user-attachments/assets/8f81d232-5813-4994-a61d-e42a05a93a3e"
    />
    
    `SubagentStop` runs when a thread-spawned subagent turn is about to
    finish. Thread-spawned subagents use `SubagentStop` instead of the
    normal root-agent `Stop` hook.
    
    Configured handlers match on `agent_type`. Hook input includes the
    normal stop fields plus:
    
    - `agent_id`: the child thread id.
    - `agent_type`: the resolved subagent type.
    - `agent_transcript_path`: the child subagent transcript path.
    - `transcript_path`: the parent thread transcript path.
    - `last_assistant_message`: the final assistant message from the child
    turn, when available.
    - `stop_hook_active`: `true` when the child is already continuing
    because an earlier stop-like hook blocked completion.
    
    `SubagentStop` shares the same completion-control semantics as `Stop`,
    scoped to the child turn:
    
    - No decision allows the child turn to finish.
    - `decision: "block"` with a non-empty `reason` records that reason as
    hook feedback and continues the child with that prompt.
    - `continue: false` stops the child turn. If `stopReason` is present,
    Codex surfaces it as the stop reason.
    
    # Lifecycle Scope
    
    Only thread-spawned subagents run `SubagentStop`.
    
    Internal/system subagents such as Review, Compact, MemoryConsolidation,
    and Other do not run normal `Stop` hooks and do not run `SubagentStop`.
    This avoids exposing synthetic matcher labels for internal
    implementation paths.
    
    # Stack
    
    1. #22782: add `SubagentStart`.
    2. This PR: add `SubagentStop`.
    3. #22882: add subagent identity to normal hook inputs.
  • core: refresh active permission profiles at runtime (#22931)
    ## Why
    
    Once a named permission profile is selected, runtime state has to keep
    that profile identity intact instead of collapsing back to anonymous
    effective permissions. The session refresh path also needs to rebuild
    profile-derived network proxy state so active profile switches take
    effect consistently.
    
    ## What changed
    
    - Preserve the active permission profile through session updates.
    - Rebuild profile-derived runtime/network configuration when the active
    profile changes.
    - Keep the runtime path aligned with the current session configuration
    APIs.
    - Tighten the affected tests, including the Windows delete-pending
    memory-file case that was intermittently tripping CI.
    
    ## Stack
    
    1. **This PR**: runtime/session/network propagation for active
    permission profiles.
    2. [#23708](https://github.com/openai/codex/pull/23708): TUI selection
    plumbing and guardrail flow.
    3. [#21559](https://github.com/openai/codex/pull/21559): profile-aware
    `/permissions` menu and custom profile display.
    
    <img width="1296" height="906" alt="image"
    src="https://github.com/user-attachments/assets/077fa3a7-80cb-4925-80b1-d2395018d90a"
    />
  • windows-sandbox: feed setup from resolved permissions (#23167)
    ## Why
    
    This is the next step in the Windows sandbox migration away from the
    legacy `SandboxPolicy` abstraction. #22923 moved write-root and token
    decisions onto `ResolvedWindowsSandboxPermissions`, but setup and
    identity still accepted `SandboxPolicy` and converted internally. This
    PR pushes that conversion outward so the setup path consumes the
    resolved Windows permission view directly.
    
    ## What Changed
    
    - Changed `SandboxSetupRequest` to carry
    `ResolvedWindowsSandboxPermissions` instead of `SandboxPolicy` plus
    policy cwd.
    - Updated setup refresh/elevation and identity credential preparation to
    use resolved permissions for read roots, write roots, network identity,
    and deny-write payload planning.
    - Removed the production `allow.rs` legacy wrapper; allow-path
    computation now takes resolved permissions directly.
    - Added a permissions-based world-writable audit entry point while
    keeping the existing legacy wrapper for compatibility.
    - Updated legacy ACL setup and the core Windows setup bridge to
    construct resolved permissions at the boundary.
    - Hardened the Windows sandbox integration test helper staging so Bazel
    retries can reuse an already-staged helper if a prior sandbox helper
    process still has the executable open.
    
    ## Verification
    
    - `cargo test -p codex-windows-sandbox`
    - `cargo test -p codex-core --test all --no-run`
    - `just fix -p codex-windows-sandbox`
    - `just fix -p codex-core`
    - Attempted `cargo check -p codex-windows-sandbox --target
    x86_64-pc-windows-gnullvm`, but the local machine is missing
    `x86_64-w64-mingw32-clang`; Windows CI should cover that target.
    
    
    
    
    
    
    
    
    
    
    
    ---
    [//]: # (BEGIN SAPLING FOOTER)
    Stack created with [Sapling](https://sapling-scm.com). Best reviewed
    with [ReviewStack](https://reviewstack.dev/openai/codex/pull/23167).
    * #23715
    * #23714
    * __->__ #23167
  • release: package prebuilt resource binaries (#23759)
    ## Why
    
    Release packaging should be a staging step once release binaries have
    already been built and signed. The Windows release job was downloading
    and signing `codex-command-runner.exe` and
    `codex-windows-sandbox-setup.exe`, but `scripts/build_codex_package.py`
    still rebuilt those helpers while creating the package archives.
    
    That makes the package step slower and, more importantly, risks putting
    helper binaries in the archive that were produced after the signing
    step. Linux had the same shape for package resources: `bwrap` could be
    rebuilt by the package builder instead of being passed in as a prebuilt
    release artifact.
    
    This builds on #23752, which fixes `.tar.zst` creation when Windows
    runners rely on the repository DotSlash `zstd` wrapper.
    
    ## What changed
    
    - Add explicit prebuilt resource inputs to the Codex package builder:
      - `--bwrap-bin`
      - `--codex-command-runner-bin`
      - `--codex-windows-sandbox-setup-bin`
    - Make `.github/scripts/build-codex-package-archive.sh` pass resource
    binaries from the release output directory when they are already
    present.
    - Build Linux `bwrap` for app-server release jobs too, so app-server
    package creation does not invoke Cargo just to supply the package
    resource.
    - Keep macOS package creation as a no-Cargo path when `--entrypoint-bin`
    is provided, since macOS packages have no resource binaries.
    - Add unit coverage showing prebuilt macOS, Linux, and Windows package
    inputs result in no source-built binaries.
    
    ## Verification
    
    - `python3 -m unittest discover -s scripts/codex_package -p 'test_*.py'`
    - `python3 -m py_compile scripts/codex_package/*.py`
    - `bash -n .github/scripts/build-codex-package-archive.sh`
    - Dry-ran Linux and Windows package builds with fake prebuilt resources
    and a nonexistent Cargo path to verify the package builder did not
    invoke Cargo.
    
    
    ---
    [//]: # (BEGIN SAPLING FOOTER)
    Stack created with [Sapling](https://sapling-scm.com). Best reviewed
    with [ReviewStack](https://reviewstack.dev/openai/codex/pull/23759).
    * #23760
    * __->__ #23759
  • chore: use Codex Linux runners for Rust releases (#23761)
    ## Why
    
    Linux release jobs build the MUSL artifacts that ship in Codex releases,
    including both the primary CLI bundle and the app-server bundle. Those
    builds should run on the Codex Linux runner pools instead of generic
    Ubuntu-hosted runners so release builds use the x64 and arm64 capacity
    intended for Codex artifacts.
    
    ## What Changed
    
    - Moves the `x86_64-unknown-linux-musl` release matrix entries in
    `.github/workflows/rust-release.yml` from `ubuntu-24.04` to
    `codex-linux-x64-xl`.
    - Moves the `aarch64-unknown-linux-musl` release matrix entries from
    `ubuntu-24.04-arm` to `codex-linux-arm64`.
    - Leaves macOS release jobs, target triples, bundle names, and artifact
    names unchanged.
    
    ## Verification
    
    - Reviewed the workflow matrix diff for
    `.github/workflows/rust-release.yml`.
    - Not run locally; this is a GitHub Actions runner configuration change.
  • windows-sandbox: drive write roots from resolved permissions (#22923)
    ## Why
    
    This is the third PR in the Windows sandbox `SandboxPolicy` ->
    `PermissionProfile` migration stack.
    
    #22896 introduced `ResolvedWindowsSandboxPermissions`, and #22918 moved
    elevated runner IPC to carry `PermissionProfile`. This PR starts moving
    the remaining setup/spawn helpers away from asking legacy enum questions
    like “is this `WorkspaceWrite`?” and toward resolved runtime permission
    questions like “does this profile require write capability roots?”
    
    ## What changed
    
    - Added resolved-permissions helpers for network identity and
    write-capability detection.
    - Moved setup write-root gathering to operate on
    `ResolvedWindowsSandboxPermissions`, with the legacy `SandboxPolicy`
    wrapper left in place for existing call sites.
    - Updated identity setup, elevated capture setup, and world-writable
    audit denies to use resolved write roots.
    - Updated spawn preparation to carry resolved permissions in
    `SpawnContext` and use them for network blocking, setup write roots,
    elevated capability SID selection, and legacy capability roots.
    - Removed a now-unused legacy write-root helper.
    
    ## Verification
    
    - `cargo test -p codex-windows-sandbox`
    - `just fix -p codex-windows-sandbox`
    - Existing stack checks are green on #22896 and #22918; CI has started
    for this PR.
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    ---
    [//]: # (BEGIN SAPLING FOOTER)
    Stack created with [Sapling](https://sapling-scm.com). Best reviewed
    with [ReviewStack](https://reviewstack.dev/openai/codex/pull/22923).
    * #23715
    * #23714
    * #23167
    * __->__ #22923
  • release: use DotSlash zstd for package archives (#23752)
    ## Why
    
    The Windows release job installed DotSlash successfully, but package
    archive creation still failed while writing `codex-package-*.tar.zst`.
    The Python archiver used `shutil.which("zstd")`, which does not reliably
    find the extensionless DotSlash manifest at `.github/workflows/zstd`
    from native Windows Python.
    
    That left release packaging dependent on a command named exactly `zstd`
    being discoverable on `PATH`, even though the repository already carries
    a DotSlash wrapper for Windows runners.
    
    ## What changed
    
    - Add `resolve_zstd_command()` to prefer a real `zstd` binary when
    present.
    - Fall back to invoking `dotslash .github/workflows/zstd` when `zstd` is
    not on `PATH`.
    - Keep the error explicit when neither `zstd` nor the DotSlash fallback
    is available.
    - Add unit coverage for direct `zstd`, DotSlash fallback, and
    missing-tool error paths.
    
    ## Verification
    
    - `python3 -m unittest discover -s scripts/codex_package -p 'test_*.py'`
    - `python3 -m py_compile scripts/codex_package/*.py`
  • Wire MITM hooks into runtime enforcement (#20659)
    ## Stack
    1. Parent PR: #18868 adds MITM hook config and model only.
    2. This PR wires runtime enforcement.
    3. User facing config follow up: #18240 moves MITM policy into the
    PermissionProfile network tree.
    
    ## Why
    1. After the hook model exists, the proxy needs a separate behavior
    change that can be tested at the request path.
    2. This PR makes hooked HTTPS hosts require MITM, evaluates inner
    requests after CONNECT, mutates headers for matching hooks, and blocks
    hooked hosts when no hook matches.
    3. It also fixes the activation path so a permission profile with MITM
    hook policy starts the managed proxy.
    4. Keeping this separate from #18868 lets reviewers focus on runtime
    effects, telemetry, and request mutation.
    
    ## Summary
    1. Store compiled MITM hooks in network proxy state.
    2. Require MITM for hooked hosts even when network mode is full.
    3. Evaluate inner HTTPS requests against host specific hooks.
    4. Apply hook actions by replacing request headers before forwarding.
    5. Block hooked hosts when no hook matches and record block telemetry.
    6. Treat profile MITM hook policy as managed proxy policy so the proxy
    starts when needed.
    7. Keep the duplicate authorization header replacement and query
    preserving request rebuild in this runtime PR.
    8. Add runtime tests and README guidance for hook enforcement.
    
    ## Validation
    1. Ran the network proxy MITM policy tests.
    2. Ran the hooked host CONNECT test.
    3. Ran the authorization header replacement test.
    4. Ran the core permission profile proxy activation test for MITM hooks.
    5. Ran the scoped Clippy fixer for the network proxy crate.
    6. Ran the scoped Clippy fixer for the core crate.
  • Support compact SessionStart hooks (#21272)
    # Why
    
    Compaction replaces the live conversation history, so hooks that use
    `SessionStart` to re-inject durable model context need a way to run
    again after that rewrite.
    
    Related - #19905 adds dedicated compact lifecycle hooks
    
    # What
    
    - add `compact` as a supported `SessionStart` source and matcher value
    - change pending `SessionStart` state from a single slot to a small FIFO
    queue so `resume` / `startup` / `clear` can be preserved alongside a
    later `compact`
    - drain all queued `SessionStart` sources before the next model request,
    preserving their original order
    
    # Testing
    
    The new integration coverage verifies both the basic `compact` matcher
    path and the stacked `resume` -> `compact` case where both hooks
    contribute `additionalContext` to the next model turn.
  • [skills] Create a personal update flow for plugin creator (#23542)
    ## Summary
    Creates a personal-marketplace update flow for the plugin-creator skill
    when iterating on an existing local plugin.
    
    ## Context
    Plugin creation already had a scaffold path, but the follow-up story for
    updating an existing local plugin during development was not explicit.
    The goal of this change is to make that default personal-marketplace
    update loop legible at the point of use instead of leaving it implied or
    hidden behind a larger helper.
    
    ## Decision
    Keep the scaffold flow intact, add a dedicated update/reinstall
    reference centered on the personal marketplace, document the actual
    `codex plugin add` and marketplace-check commands directly, and keep
    helper automation narrowly scoped to the repetitive local-update steps.
    
    ## Changes
    - update plugin-creator to point existing-plugin iteration at a
    personal-marketplace update flow
    - add `references/installing-and-updating.md` with the explicit
    marketplace check and reinstall sequence
    - add small helper scripts for reading marketplace names and updating
    plugin versions during local iteration
    
    ## Tests
    - `python3
    codex-rs/skills/src/assets/samples/skill-creator/scripts/quick_validate.py
    codex-rs/skills/src/assets/samples/plugin-creator`
    - `python3 -m py_compile
    codex-rs/skills/src/assets/samples/plugin-creator/scripts/create_basic_plugin.py
    codex-rs/skills/src/assets/samples/plugin-creator/scripts/read_marketplace_name.py
    codex-rs/skills/src/assets/samples/plugin-creator/scripts/update_plugin_cachebuster.py`
  • cli: add strict config to exec-server (#23719)
    ## Why
    
    PR #20559 added opt-in strict config parsing to the config-loading
    command surfaces, but `codex exec-server` was left out. That meant
    `codex exec-server --strict-config` was rejected even though the command
    can load config for remote registration, and local server startup had no
    way to fail fast on misspelled config keys.
    
    ## What Changed
    
    - Added `--strict-config` to `codex exec-server`.
    - Allowed root-level inheritance from `codex --strict-config
    exec-server`.
    - Validated config before local exec-server startup when strict mode is
    requested.
    - Reused the loaded strict-config-aware config for remote exec-server
    registration auth.
    - Added CLI coverage showing `codex exec-server --strict-config` rejects
    unknown config fields.
    
    ## Verification
    
    - `cargo test -p codex-cli`
    - New integration test:
    `strict_config_rejects_unknown_config_fields_for_exec_server`
    
    ## Documentation
    
    Any strict-config command list on developers.openai.com/codex should
    include `codex exec-server` with the other supported config-loading
    entry points.
  • feat(permissions): resolve permission profile inheritance (#22270)
    ## Stack
    
    This is the foundation PR for the permission-profile inheritance stack.
    
    - This PR adds config-level `extends` resolution and merge semantics.
    - Follow-up: #23705 applies resolved profiles at runtime and updates the
    active-profile protocol surfaces.
    
    ## Why
    
    Permission profiles are starting to carry enough policy that
    copy-pasting near-identical definitions becomes hard to review and easy
    to drift. Before the runtime can consume inherited profiles, the config
    layer needs one explicit resolver that can merge parent chains and
    reject unsafe or invalid inheritance shapes.
    
    ## What changed
    
    - Add `extends` to permission-profile TOML and resolve parent chains in
    inheritance order.
    - Merge inherited profile TOML with the existing config merge behavior
    while preserving the permission-specific normalization needed for
    network domain keys.
    - Keep parent descriptions out of resolved child profiles and record
    inherited profile names separately for downstream consumers.
    - Reject undefined parents, unsupported built-in parents, and
    inheritance cycles with targeted errors.
    - Cover resolver behavior with TOML fixture tests and refresh the
    generated config schema.
    
    ## Validation
    
    - `cargo test -p codex-config`
    - `cargo test -p codex-core permissions_profiles_`
  • Add MITM hook config model (#18868)
    ## Stack
    1. This PR adds MITM hook config and model only.
    2. Runtime follow up: #20659 wires hook enforcement into the proxy
    request path.
    3. User facing config follow up: #18240 moves MITM policy into the
    PermissionProfile network tree.
    
    ## Why
    1. Viyat asked for the original parent PR to be split so reviewers can
    inspect the policy model before request behavior changes.
    2. This PR gives the proxy a typed MITM hook model, validation, matcher
    compilation, permissions TOML plumbing, schema support, and config
    tests.
    3. This PR deliberately does not change CONNECT or MITM request
    handling.
    4. Keeping runtime behavior out of this PR makes the review boundary
    simple: does the policy model parse, validate, compile, and lower
    correctly.
    
    ## Summary
    1. Add the MITM hook config model and matcher compilation.
    2. Validate hosts, methods, paths, query matchers, header matchers,
    secret sources, and reserved body matching.
    3. Add wildcard matcher support for path, query value, and header value
    matching.
    4. Add permissions TOML and schema support for flat runtime hook config.
    5. Add config loader tests for MITM hook overlay behavior.
    
    ## Validation
    1. Regenerated the config schema.
    2. Ran the network proxy MITM hook unit tests.
    3. Ran the core permission profile MITM hook parsing tests.
    4. Ran the core config schema fixture test.
    5. Ran the scoped Clippy fixer for the network proxy crate.
    6. Ran the scoped Clippy fixer for the core crate.
    
    ## Notes
    1. Runtime enforcement moved to #20659.
    2. User facing PermissionProfile TOML shape remains in #18240.
  • windows-sandbox: share bundled helper lookup (#23735)
    ## Summary
    
    Follow-up to #23636 review feedback: the Windows sandbox had two copies
    of the same bundled-helper lookup order, one for
    `codex-command-runner.exe` in `helper_materialization.rs` and one for
    `codex-windows-sandbox-setup.exe` in `setup.rs`.
    
    This PR centralizes that lookup in
    `helper_materialization::bundled_executable_path_for_exe()` and has
    setup reuse it for `codex-windows-sandbox-setup.exe`. The lookup
    behavior is unchanged: direct sibling first, package-root
    `codex-resources/` when running from `bin/`, then legacy sibling
    `codex-resources/`.
    
    ## Test plan
    
    - `cargo test -p codex-windows-sandbox`
    
    ## Notes
    
    I also attempted `cargo check -p codex-windows-sandbox --target
    x86_64-pc-windows-gnullvm`, but this local host is missing
    `x86_64-w64-mingw32-clang`.
  • windows-sandbox: send permission profiles to elevated runner (#22918)
    ## Why
    
    This is the next PR in the Windows sandbox migration stack after #22896.
    The bottom PR introduces a Windows-local resolved permissions helper
    while existing callers still start from legacy `SandboxPolicy`. This PR
    moves the elevated runner IPC boundary to `PermissionProfile`, which
    makes the direction of the stack visible without changing the public
    core call sites yet.
    
    Because that changes the CLI-to-command-runner message shape, the framed
    IPC protocol version is bumped in the same PR so the boundary change is
    explicit.
    
    ## What changed
    
    - Replaced elevated IPC `policy_json_or_preset`/`sandbox_policy_cwd`
    fields with `permission_profile`/`permission_profile_cwd`.
    - Bumped the elevated command-runner IPC protocol to
    `IPC_PROTOCOL_VERSION = 2` and switched parent/runner frames to use the
    shared constant.
    - Converted the parent elevated paths from the parsed legacy policy into
    a materialized `PermissionProfile` before sending the runner request.
    - Added `WindowsSandboxTokenMode` resolution for managed
    `PermissionProfile` values and made the runner choose read-only vs
    writable-root capability tokens from that resolved profile.
    - Rejected disabled, external, unrestricted, and full-disk-write
    profiles before token selection.
    - Added IPC JSON coverage for tagged `PermissionProfile` payloads and
    token-mode unit coverage for the resolved permission helper.
    
    ## Verification
    
    - `cargo test -p codex-windows-sandbox`
    - `just fix -p codex-windows-sandbox`
    - `cargo check -p codex-windows-sandbox --target x86_64-pc-windows-msvc
    --tests` was attempted locally but blocked before crate type-checking
    because the macOS compiler environment lacks Windows C headers such as
    `windows.h` and `assert.h`; GitHub Windows CI is the required
    verification for the runner path.
    
    ---
    [//]: # (BEGIN SAPLING FOOTER)
    Stack created with [Sapling](https://sapling-scm.com). Best reviewed
    with [ReviewStack](https://reviewstack.dev/openai/codex/pull/22918).
    * #23715
    * #23714
    * #23167
    * #22923
    * __->__ #22918
  • dotslash: publish Codex entrypoints from package archives (#23638)
    ## Summary
    
    DotSlash should resolve the same canonical package archives used by
    standalone installers and npm platform packages, rather than continuing
    to point at single-binary zstd artifacts or the older Linux bundle
    archive.
    
    This updates the Codex CLI and `codex-app-server` DotSlash release
    config entries to match `codex-package-<target>.tar.gz` and
    `codex-app-server-package-<target>.tar.gz`, with paths that select
    `bin/codex` or `bin/codex-app-server` inside the extracted package. The
    other helper outputs stay on their existing per-binary artifacts for
    now.
    
    ## Test plan
    
    - `python3 -m json.tool .github/dotslash-config.json > /dev/null`
    - Ran a Python regex smoke test that checked every updated `codex` and
    `codex-app-server` platform entry against the archive names emitted by
    `.github/scripts/build-codex-package-archive.sh`.
  • fix(config): resolve cloud requirements deny-read globs (#23729)
    ## Why
    
    Cloud-managed `requirements.toml` contents were deserialized without an
    `AbsolutePathBuf` base directory. Relative managed
    `permissions.filesystem.deny_read` glob entries therefore failed while
    the equivalent local system requirements path succeeded under its
    `AbsolutePathBufGuard`. This follows the `codex_home` base path
    convention clarified in https://github.com/openai/codex/pull/15707.
    
    ## What changed
    
    - Resolve cloud requirements TOML under an `AbsolutePathBufGuard` rooted
    at `codex_home`.
    - Reuse the same base for cloud requirements loaded from the signed
    cache.
    - Add a regression test for a relative cloud-managed `deny_read` glob.
    
    ## Validation
    
    - `just fmt`
    - `cargo test -p codex-cloud-requirements`
    - `cargo clippy -p codex-cloud-requirements --all-targets --no-deps`
    - `just bazel-lock-update`
    - `just bazel-lock-check`
    - `git diff --check`
  • npm: ship platform packages in Codex package layout (#23637)
    ## Summary
    
    The npm platform packages should stop carrying a bespoke native layout
    now that the release workflow builds canonical Codex package archives.
    Keeping npm on the same `bin/`, `codex-resources/`, and `codex-path/`
    structure lets the Rust package-layout detection behave consistently
    across standalone, npm, and future DotSlash installs.
    
    This changes platform npm packages to stage the `codex-package` artifact
    for each target under `vendor/<target>`. The Node launcher now resolves
    `bin/codex` and prepends `codex-path`, while retaining legacy
    `vendor/<target>/codex` and `vendor/<target>/path` fallback support for
    local development and migration. The npm staging helper downloads
    `codex-package` archives instead of rebuilding the CLI payload from
    individual `codex`, `rg`, `bwrap`, and sandbox helper artifacts.
    
    CI still needs to stage npm packages from historical rust-release
    workflow artifacts that predate package archives, so the staging scripts
    expose an explicit `--allow-legacy-codex-package` fallback. That
    fallback synthesizes the canonical package layout from legacy per-binary
    artifacts and is wired only into the CI smoke path; release staging
    remains strict and continues to require real package archives.
    
    For direct local use, `install_native_deps.py` now points its built-in
    default workflow at the same recent artifact run used by CI and
    automatically enables legacy package synthesis only when
    `--workflow-url` is omitted. Explicit workflow URLs remain strict unless
    callers opt in with `--allow-legacy-codex-package`.
    
    ## Test plan
    
    - `python3 -m py_compile codex-cli/scripts/build_npm_package.py
    codex-cli/scripts/install_native_deps.py scripts/stage_npm_packages.py
    scripts/codex_package/cli.py`
    - `node --check codex-cli/bin/codex.js`
    - `ruby -e 'require "yaml";
    YAML.load_file(".github/workflows/rust-release.yml");
    YAML.load_file(".github/workflows/ci.yml"); puts "ok"'`
    - Staged a synthetic `codex-linux-x64` platform package from a canonical
    vendor tree and verified it copied only `bin/`, `codex-path/`,
    `codex-resources/`, and `codex-package.json`.
    - Imported `install_native_deps.py` and extracted a synthetic
    `codex-package-x86_64-unknown-linux-musl.tar.gz` into `vendor/<target>`.
    - Ran legacy-layout conversion smokes for Linux, Windows, and unsigned
    macOS artifact naming.
    - Ran a synthetic `install_native_deps.py` default-workflow smoke that
    verifies legacy package synthesis is automatic only when
    `--workflow-url` is omitted.
    - `NPM_CONFIG_CACHE="$tmp_dir/npm-cache" python3
    ./scripts/stage_npm_packages.py --release-version 0.125.0 --workflow-url
    https://github.com/openai/codex/actions/runs/26131514935 --package codex
    --allow-legacy-codex-package --output-dir "$tmp_dir"`
    - `node codex-cli/bin/codex.js --version`
    
    
    ---
    [//]: # (BEGIN SAPLING FOOTER)
    Stack created with [Sapling](https://sapling-scm.com). Best reviewed
    with [ReviewStack](https://reviewstack.dev/openai/codex/pull/23637).
    * #23638
    * __->__ #23637