Commit Graph

489 Commits

  • feat(approvals) RejectConfig for request_permissions (#14118)
    ## Summary
    We need to support allowing request_permissions calls when using
    `Reject` policy
    
    <img width="1133" height="588" alt="Screenshot 2026-03-09 at 12 06
    40 PM"
    src="https://github.com/user-attachments/assets/a8df987f-c225-4866-b8ab-5590960daec5"
    />
    
    Note that this is a backwards-incompatible change for Reject policy. I'm
    not sure if we need to add a default based on our current use/setup
    
    ## Testing
    - [x] Added tests
    - [x] Tested locally
  • fix(core) RequestPermissions + ApplyPatch (#14055)
    ## Summary
    The apply_patch tool should also respect AdditionalPermissions
    
    ## Testing
    - [x] Added unit tests
    
    ---------
    
    Co-authored-by: Codex <noreply@openai.com>
  • fix(core): use dedicated types for responsesapi web search tool config (#14136)
    This changes the web_search tool spec in codex-core to use dedicated
    Responses-API payload structs instead of shared config types and custom
    serializers.
    
    Previously, `ToolSpec::WebSearch` stored `WebSearchFilters` and
    `WebSearchUserLocation` directly and relied on hand-written serializers
    to shape the outgoing JSON. This worked, but it mixed config/schema
    types with the OpenAI Responses payload contract and created an easy
    place for drift if those shared types changed later.
    
    ### Why
    This keeps the boundary clearer:
    - app-server/config/schema types stay focused on config
    - Responses tool payload types stay focused on the OpenAI wire format
    
    It also makes the serialization behavior obvious from the structs
    themselves, instead of hiding it in custom serializer functions.
  • feat(core) Persist request_permission data across turns (#14009)
    ## Summary
    request_permissions flows should support persisting results for the
    session.
    
    Open Question: Still deciding if we need within-turn approvals - this
    adds complexity but I could see it being useful
    
    ## Testing
    - [x] Updated unit tests
    
    ---------
    
    Co-authored-by: Codex <noreply@openai.com>
  • Stabilize guardian approval coverage (#14103)
    ## Summary
    - align the guardian permission test with the actual sandbox policy it
    widens and use a slightly larger Windows-only timeout budget
    - expose the additional-permissions normalization helper to the guardian
    test module
    - replace the guardian popup snapshot assertion with targeted string
    assertions
    
    ## Why this fixes the flake
    This group was carrying two separate sources of drift. The guardian core
    test widened derived sandbox policies without updating the source
    sandbox policy, and it used a Windows command/timeout combination that
    was too tight on slower runners. Separately, the TUI test was
    snapshotting the full popup even though unrelated feature text changes
    were the only thing moving. The new assertions keep coverage on the
    guardian entry itself while removing unrelated snapshot churn.
  • guardian initial feedback / tweaks (#13897)
    ## Summary
    - remove the remaining model-visible guardian-specific `on-request`
    prompt additions so enabling the feature does not change the main
    approval-policy instructions
    - neutralize user-facing guardian wording to talk about automatic
    approval review / approval requests rather than a second reviewer or
    only sandbox escalations
    - tighten guardian retry-context handling so agent-authored
    `justification` stays in the structured action JSON and is not also
    injected as raw retry context
    - simplify guardian review plumbing in core by deleting dead
    prompt-append paths and trimming some request/transcript setup code
    
    ## Notable Changes
    - delete the dead `permissions/approval_policy/guardian.md` append path
    and stop threading `guardian_approval_enabled` through model-facing
    developer-instruction builders
    - rename the experimental feature copy to `Automatic approval review`
    and update the `/experimental` snapshot text accordingly
    - make approval-review status strings generic across shell, patch,
    network, and MCP review types
    - forward real sandbox/network retry reasons for shell and unified-exec
    guardian review, but do not pass agent-authored justification as raw
    retry context
    - simplify `guardian.rs` by removing the one-field request wrapper,
    deduping reasoning-effort selection, and cleaning up transcript entry
    collection
    
    ## Testing
    - `just fmt`
    - full validation left to CI
    
    ---------
    
    Co-authored-by: Codex <noreply@openai.com>
  • Add request permissions tool (#13092)
    Adds a built-in `request_permissions` tool and wires it through the
    Codex core, protocol, and app-server layers so a running turn can ask
    the client for additional permissions instead of relying on a static
    session policy.
    
    The new flow emits a `RequestPermissions` event from core, tracks the
    pending request by call ID, forwards it through app-server v2 as an
    `item/permissions/requestApproval` request, and resumes the tool call
    once the client returns an approved subset of the requested permission
    profile.
  • app-server: include experimental skill metadata in exec approval requests (#13929)
    ## Summary
    
    This change surfaces skill metadata on command approval requests so
    app-server clients can tell when an approval came from a skill script
    and identify the originating `SKILL.md`.
    
    - add `skill_metadata` to exec approval events in the shared protocol
    - thread skill metadata through core shell escalation and delegated
    approval handling for skill-triggered approvals
    - expose the field in app-server v2 as experimental `skillMetadata`
    - regenerate the JSON/TypeScript schemas and cover the new field in
    protocol, transport, core, and TUI tests
    
    ## Why
    
    Skill-triggered approvals already carry skill context inside core, but
    app-server clients could not see which skill caused the prompt. Sending
    the skill metadata with the approval request makes it possible for
    clients to present better approval UX and connect the prompt back to the
    relevant skill definition.
    
    
    ## example event in app-server-v2
    verified that we see this event when experimental api is on:
    ```
    < {
    <   "id": 11,
    <   "method": "item/commandExecution/requestApproval",
    <   "params": {
    <     "additionalPermissions": {
    <       "fileSystem": null,
    <       "macos": {
    <         "accessibility": false,
    <         "automations": {
    <           "bundle_ids": [
    <             "com.apple.Notes"
    <           ]
    <         },
    <         "calendar": false,
    <         "preferences": "read_only"
    <       },
    <       "network": null
    <     },
    <     "approvalId": "25d600ee-5a3c-4746-8d17-e2e61fb4c563",
    <     "availableDecisions": [
    <       "accept",
    <       "acceptForSession",
    <       "cancel"
    <     ],
    <     "command": "/Applications/ChatGPT.app/Contents/Resources/CodexAppServer_CodexAppServerBundledSkills.bundle/Contents/Resources/skills/apple-notes/scripts/notes_info",
    <     "commandActions": [
    <       {
    <         "command": "/Applications/ChatGPT.app/Contents/Resources/CodexAppServer_CodexAppServerBundledSkills.bundle/Contents/Resources/skills/apple-notes/scripts/notes_info",
    <         "type": "unknown"
    <       }
    <     ],
    <     "cwd": "/Applications/ChatGPT.app/Contents/Resources/CodexAppServer_CodexAppServerBundledSkills.bundle/Contents/Resources/skills/apple-notes",
    <     "itemId": "call_jZp3xFpNg4D8iKAD49cvEvZy",
    <     "skillMetadata": {
    <       "pathToSkillsMd": "/Applications/ChatGPT.app/Contents/Resources/CodexAppServer_CodexAppServerBundledSkills.bundle/Contents/Resources/skills/apple-notes/SKILL.md"
    <     },
    <     "threadId": "019ccc10-b7d3-7ff2-84fe-3a75e7681e69",
    <     "turnId": "019ccc10-b848-76f1-81b3-4a1fa225493f"
    <   }
    < }`
    ```
    
    & verified that this is the event when experimental api is off:
    ```
    < {
    <   "id": 13,
    <   "method": "item/commandExecution/requestApproval",
    <   "params": {
    <     "approvalId": "5fbbf776-261b-4cf8-899b-c125b547f2c0",
    <     "availableDecisions": [
    <       "accept",
    <       "acceptForSession",
    <       "cancel"
    <     ],
    <     "command": "/Applications/ChatGPT.app/Contents/Resources/CodexAppServer_CodexAppServerBundledSkills.bundle/Contents/Resources/skills/apple-notes/scripts/notes_info",
    <     "commandActions": [
    <       {
    <         "command": "/Applications/ChatGPT.app/Contents/Resources/CodexAppServer_CodexAppServerBundledSkills.bundle/Contents/Resources/skills/apple-notes/scripts/notes_info",
    <         "type": "unknown"
    <       }
    <     ],
    <     "cwd": "/Users/celia/code/codex/codex-rs",
    <     "itemId": "call_OV2DHzTgYcbYtWaTTBWlocOt",
    <     "threadId": "019ccc16-2a2b-7be1-8500-e00d45b892d4",
    <     "turnId": "019ccc16-2a8e-7961-98ec-649600e7d06a"
    <   }
    < }
    ```
  • seatbelt: honor split filesystem sandbox policies (#13448)
    ## Why
    
    After `#13440` and `#13445`, macOS Seatbelt policy generation was still
    deriving filesystem and network behavior from the legacy `SandboxPolicy`
    projection.
    
    That projection loses explicit unreadable carveouts and conflates split
    network decisions, so the generated Seatbelt policy could still be wider
    than the split policy that Codex had already computed.
    
    ## What changed
    
    - added Seatbelt entrypoints that accept `FileSystemSandboxPolicy` and
    `NetworkSandboxPolicy` directly
    - built read and write policy stanzas from access roots plus excluded
    subpaths so explicit unreadable carveouts survive into the generated
    Seatbelt policy
    - switched network policy generation to consult `NetworkSandboxPolicy`
    directly
    - failed closed when managed-network or proxy-constrained sessions do
    not yield usable loopback proxy endpoints
    - updated the macOS callers and test helpers that now need to carry the
    split policies explicitly
    
    ## Verification
    
    - added regression coverage in `core/src/seatbelt.rs` for unreadable
    carveouts under both full-disk and scoped-readable policies
    - verified the current PR state with `just clippy`
    
    
    
    
    ---
    [//]: # (BEGIN SAPLING FOOTER)
    Stack created with [Sapling](https://sapling-scm.com). Best reviewed
    with [ReviewStack](https://reviewstack.dev/openai/codex/pull/13448).
    * #13453
    * #13452
    * #13451
    * #13449
    * __->__ #13448
    * #13445
    * #13440
    * #13439
    
    ---------
    
    Co-authored-by: viyatb-oai <viyatb@openai.com>
  • Add guardian approval MVP (#13692)
    ## Summary
    - add the guardian reviewer flow for `on-request` approvals in command,
    patch, sandbox-retry, and managed-network approval paths
    - keep guardian behind `features.guardian_approval` instead of exposing
    a public `approval_policy = guardian` mode
    - route ordinary `OnRequest` approvals to the guardian subagent when the
    feature is enabled, without changing the public approval-mode surface
    
    ## Public model
    - public approval modes stay unchanged
    - guardian is enabled via `features.guardian_approval`
    - when that feature is on, `approval_policy = on-request` keeps the same
    approval boundaries but sends those approval requests to the guardian
    reviewer instead of the user
    - `/experimental` only persists the feature flag; it does not rewrite
    `approval_policy`
    - CLI and app-server no longer expose a separate `guardian` approval
    mode in this PR
    
    ## Guardian reviewer
    - the reviewer runs as a normal subagent and reuses the existing
    subagent/thread machinery
    - it is locked to a read-only sandbox and `approval_policy = never`
    - it does not inherit user/project exec-policy rules
    - it prefers `gpt-5.4` when the current provider exposes it, otherwise
    falls back to the parent turn's active model
    - it fail-closes on timeout, startup failure, malformed output, or any
    other review error
    - it currently auto-approves only when `risk_score < 80`
    
    ## Review context and policy
    - guardian mirrors `OnRequest` approval semantics rather than
    introducing a separate approval policy
    - explicit `require_escalated` requests follow the same approval surface
    as `OnRequest`; the difference is only who reviews them
    - managed-network allowlist misses that enter the approval flow are also
    reviewed by guardian
    - the review prompt includes bounded recent transcript history plus
    recent tool call/result evidence
    - transcript entries and planned-action strings are truncated with
    explicit `<guardian_truncated ... />` markers so large payloads stay
    bounded
    - apply-patch reviews include the full patch content (without
    duplicating the structured `changes` payload)
    - the guardian request layout is snapshot-tested using the same
    model-visible Responses request formatter used elsewhere in core
    
    ## Guardian network behavior
    - the guardian subagent inherits the parent session's managed-network
    allowlist when one exists, so it can use the same approved network
    surface while reviewing
    - exact session-scoped network approvals are copied into the guardian
    session with protocol/port scope preserved
    - those copied approvals are now seeded before the guardian's first turn
    is submitted, so inherited approvals are available during any immediate
    review-time checks
    
    ## Out of scope / follow-ups
    - the sandbox-permission validation split was pulled into a separate PR
    and is not part of this diff
    - a future follow-up can enable `serde_json` preserve-order in
    `codex-core` and then simplify the guardian action rendering further
    
    ---------
    
    Co-authored-by: Codex <noreply@openai.com>
  • fix(core): respect reject policy by approval source for skill scripts (#13816)
    ## Summary
    - distinguish reject-policy handling for prefix-rule approvals versus
    sandbox approvals in Unix shell escalation
    - keep prompting for skill-script execution when `rules=true` but
    `sandbox_approval=false`, instead of denying the command up front
    - add regression coverage for both skill-script reject-policy paths in
    `codex-rs/core/tests/suite/skill_approval.rs`
  • sandboxing: plumb split sandbox policies through runtime (#13439)
    ## Why
    
    `#13434` introduces split `FileSystemSandboxPolicy` and
    `NetworkSandboxPolicy`, but the runtime still made most execution-time
    sandbox decisions from the legacy `SandboxPolicy` projection.
    
    That projection loses information about combinations like unrestricted
    filesystem access with restricted network access. In practice, that
    means the runtime can choose the wrong platform sandbox behavior or set
    the wrong network-restriction environment for a command even when config
    has already separated those concerns.
    
    This PR carries the split policies through the runtime so sandbox
    selection, process spawning, and exec handling can consult the policy
    that actually matters.
    
    ## What changed
    
    - threaded `FileSystemSandboxPolicy` and `NetworkSandboxPolicy` through
    `TurnContext`, `ExecRequest`, sandbox attempts, shell escalation state,
    unified exec, and app-server exec overrides
    - updated sandbox selection in `core/src/sandboxing/mod.rs` and
    `core/src/exec.rs` to key off `FileSystemSandboxPolicy.kind` plus
    `NetworkSandboxPolicy`, rather than inferring behavior only from the
    legacy `SandboxPolicy`
    - updated process spawning in `core/src/spawn.rs` and the platform
    wrappers to use `NetworkSandboxPolicy` when deciding whether to set
    `CODEX_SANDBOX_NETWORK_DISABLED`
    - kept additional-permissions handling and legacy `ExternalSandbox`
    compatibility projections aligned with the split policies, including
    explicit user-shell execution and Windows restricted-token routing
    - updated callers across `core`, `app-server`, and `linux-sandbox` to
    pass the split policies explicitly
    
    ## Verification
    
    - added regression coverage in `core/tests/suite/user_shell_cmd.rs` to
    verify `RunUserShellCommand` does not inherit
    `CODEX_SANDBOX_NETWORK_DISABLED` from the active turn
    - added coverage in `core/src/exec.rs` for Windows restricted-token
    sandbox selection when the legacy projection is `ExternalSandbox`
    - updated Linux sandbox coverage in
    `linux-sandbox/tests/suite/landlock.rs` to exercise the split-policy
    exec path
    - verified the current PR state with `just clippy`
    
    
    
    
    ---
    [//]: # (BEGIN SAPLING FOOTER)
    Stack created with [Sapling](https://sapling-scm.com). Best reviewed
    with [ReviewStack](https://reviewstack.dev/openai/codex/pull/13439).
    * #13453
    * #13452
    * #13451
    * #13449
    * #13448
    * #13445
    * #13440
    * __->__ #13439
    
    ---------
    
    Co-authored-by: viyatb-oai <viyatb@openai.com>
  • Allow full web search tool config (#13675)
    Previously, we could only configure whether web search was on/off.
    
    This PR enables sending along a web search config, which includes all
    the stuff responsesapi supports: filters, location, etc.
  • fix(core): skip exec approval for permissionless skill scripts (#13791)
    ## Summary
    
    - Treat skill scripts with no permission profile, or an explicitly empty
    one, as permissionless and run them with the turn's existing sandbox
    instead of forcing an exec approval prompt.
    - Keep the approval flow unchanged for skills that do declare additional
    permissions.
    - Update the skill approval tests to assert that permissionless skill
    scripts do not prompt on either the initial run or a rerun.
    
    ## Why
    
    Permissionless skills should inherit the current turn sandbox directly.
    Prompting for exec approval in that case adds friction without granting
    any additional capability.
  • chore(otel): rename OtelManager to SessionTelemetry (#13808)
    ## Summary
    This is a purely mechanical refactor of `OtelManager` ->
    `SessionTelemetry` to better convey what the struct is doing. No
    behavior change.
    
    ## Why
    
    `OtelManager` ended up sounding much broader than what this type
    actually does. It doesn't manage OTEL globally; it's the session-scoped
    telemetry surface for emitting log/trace events and recording metrics
    with consistent session metadata (`app_version`, `model`, `slug`,
    `originator`, etc.).
    
    `SessionTelemetry` is a more accurate name, and updating the call sites
    makes that boundary a lot easier to follow.
    
    ## Validation
    
    - `just fmt`
    - `cargo test -p codex-otel`
    - `cargo test -p codex-core`
  • config: add initial support for the new permission profile config language in config.toml (#13434)
    ## Why
    
    `SandboxPolicy` currently mixes together three separate concerns:
    
    - parsing layered config from `config.toml`
    - representing filesystem sandbox state
    - carrying basic network policy alongside filesystem choices
    
    That makes the existing config awkward to extend and blocks the new TOML
    proposal where `[permissions]` becomes a table of named permission
    profiles selected by `default_permissions`. (The idea is that if
    `default_permissions` is not specified, we assume the user is opting
    into the "traditional" way to configure the sandbox.)
    
    This PR adds the config-side plumbing for those profiles while still
    projecting back to the legacy `SandboxPolicy` shape that the current
    macOS and Linux sandbox backends consume.
    
    It also tightens the filesystem profile model so scoped entries only
    exist for `:project_roots`, and so nested keys must stay within a
    project root instead of using `.` or `..` traversal.
    
    This drops support for the short-lived `[permissions.network]` in
    `config.toml` because now that would be interpreted as a profile named
    `network` within `[permissions]`.
    
    ## What Changed
    
    - added `PermissionsToml`, `PermissionProfileToml`,
    `FilesystemPermissionsToml`, and `FilesystemPermissionToml` so config
    can parse named profiles under `[permissions.<profile>.filesystem]`
    - added top-level `default_permissions` selection, validation for
    missing or unknown profiles, and compilation from a named profile into
    split `FileSystemSandboxPolicy` and `NetworkSandboxPolicy` values
    - taught config loading to choose between the legacy `sandbox_mode` path
    and the profile-based path without breaking legacy users
    - introduced `codex-protocol::permissions` for the split filesystem and
    network sandbox types, and stored those alongside the legacy projected
    `sandbox_policy` in runtime `Permissions`
    - modeled `FileSystemSpecialPath` so only `ProjectRoots` can carry a
    nested `subpath`, matching the intended config syntax instead of
    allowing invalid states for other special paths
    - restricted scoped filesystem maps to `:project_roots`, with validation
    that nested entries are non-empty descendant paths and cannot use `.` or
    `..` to escape the project root
    - kept existing runtime consumers working by projecting
    `FileSystemSandboxPolicy` back into `SandboxPolicy`, with an explicit
    error for profiles that request writes outside the workspace root
    - loaded proxy settings from top-level `[network]`
    - regenerated `core/config.schema.json`
    
    ## Verification
    
    - added config coverage for profile deserialization,
    `default_permissions` selection, top-level `[network]` loading, network
    enablement, rejection of writes outside the workspace root, rejection of
    nested entries for non-`:project_roots` special paths, and rejection of
    parent-directory traversal in `:project_roots` maps
    - added protocol coverage for the legacy bridge rejecting non-workspace
    writes
    
    ## Docs
    
    - update the Codex config docs on developers.openai.com/codex to
    document named `[permissions.<profile>]` entries, `default_permissions`,
    scoped `:project_roots` syntax, the descendant-path restriction for
    nested `:project_roots` entries, and top-level `[network]` proxy
    configuration
    
    
    
    
    
    
    ---
    [//]: # (BEGIN SAPLING FOOTER)
    Stack created with [Sapling](https://sapling-scm.com). Best reviewed
    with [ReviewStack](https://reviewstack.dev/openai/codex/pull/13434).
    * #13453
    * #13452
    * #13451
    * #13449
    * #13448
    * #13445
    * #13440
    * #13439
    * __->__ #13434
  • feat: structured plugin parsing (#13711)
    #### What
    
    Add structured `@plugin` parsing and TUI support for plugin mentions.
    
    - Core: switch from plain-text `@display_name` parsing to structured
    `plugin://...` mentions via `UserInput::Mention` and
    `[$...](plugin://...)` links in text, same pattern as apps/skills.
    - TUI: add plugin mention popup, autocomplete, and chips when typing
    `$`. Load plugin capability summaries and feed them into the composer;
    plugin mentions appear alongside skills and apps.
    - Generalize mention parsing to a sigil parameter, still defaults to `$`
    
    <img width="797" height="119" alt="image"
    src="https://github.com/user-attachments/assets/f0fe2658-d908-4927-9139-73f850805ceb"
    />
    
    Builds on #13510. Currently clients have to build their own `id` via
    `plugin@marketplace` and filter plugins to show by `enabled`, but we
    will add `id` and `available` as fields returned from `plugin/list`
    soon.
    
    ####Tests
    
    Added tests, verified locally.
  • Clarify sandbox permission override helper semantics (#13703)
    ## Summary
    Today `SandboxPermissions::requires_additional_permissions()` does not
    actually mean "is `WithAdditionalPermissions`". It returns `true` for
    any non-default sandbox override, including `RequireEscalated`. That
    broad behavior is relied on in multiple `main` callsites.
    
    The naming is security-sensitive because `SandboxPermissions` is used on
    shell-like tool calls to tell the executor how a single command should
    relate to the turn sandbox:
    - `UseDefault`: run with the turn sandbox unchanged
    - `RequireEscalated`: request execution outside the sandbox
    - `WithAdditionalPermissions`: stay sandboxed but widen permissions for
    that command only
    
    ## Problem
    The old helper name reads as if it only applies to the
    `WithAdditionalPermissions` variant. In practice it means "this command
    requested any explicit sandbox override."
    
    That ambiguity made it easy to read production checks incorrectly and
    made the guardian change look like a standalone `main` fix when it is
    not.
    
    On `main` today:
    - `shell` and `unified_exec` intentionally reject any explicit
    `sandbox_permissions` request unless approval policy is `OnRequest`
    - `exec_policy` intentionally treats any explicit sandbox override as
    prompt-worthy in restricted sandboxes
    - tests intentionally serialize both `RequireEscalated` and
    `WithAdditionalPermissions` as explicit sandbox override requests
    
    So changing those callsites from the broad helper to a narrow
    `WithAdditionalPermissions` check would be a behavior change, not a pure
    cleanup.
    
    ## What This PR Does
    - documents `SandboxPermissions` as a per-command sandbox override, not
    a generic permissions bag
    - adds `requests_sandbox_override()` for the broad meaning: anything
    except `UseDefault`
    - adds `uses_additional_permissions()` for the narrow meaning: only
    `WithAdditionalPermissions`
    - keeps `requires_additional_permissions()` as a compatibility alias to
    the broad meaning for now
    - updates the current broad callsites to use the accurately named broad
    helper
    - adds unit coverage that locks in the semantics of all three helpers
    
    ## What This PR Does Not Do
    This PR does not change runtime behavior. That is intentional.
    
    ---------
    
    Co-authored-by: Codex <noreply@openai.com>
  • Enabling CWD Saving for Image-Gen (#13607)
    Codex now saves the generated image on to your current working
    directory.
  • copy current exe to CODEX_HOME/.sandbox-bin for apply_patch (#13669)
    We do this for codex-command-runner.exe as well for the same reason.
    Windows sandbox users cannot execute binaries in the WindowsApp/
    installed directory for the Codex App. This causes apply-patch to fail
    because it tries to execute codex.exe as the sandbox user.
  • feat(otel): safe tracing (#13626)
    ### Motivation
    Today config.toml has three different OTEL knobs under `[otel]`:
    - `exporter` controls where OTEL logs go
    - `trace_exporter` controls where OTEL traces go
    - `metrics_exporter` controls where metrics go
    
    Those often (pretty much always?) serve different purposes.
    
    For example, for OpenAI internal usage, the **log exporter** is already
    being used for IT/security telemetry, and that use case is intentionally
    content-rich: tool calls, arguments, outputs, MCP payloads, and in some
    cases user content are all useful there. `log_user_prompt` is a good
    example of that distinction. When it’s enabled, we include raw prompt
    text in OTEL logs, which is acceptable for the security use case.
    
    The **trace exporter** is a different story. The goal there is to give
    OpenAI engineers visibility into latency and request behavior when they
    run Codex locally, without sending sensitive prompt or tool data as
    trace event data. In other words, traces should help answer “what was
    slow?” or “where did time go?”, not “what did the user say?” or “what
    did the tool return?”
    
    The complication is that Rust’s `tracing` crate does not make a hard
    distinction between “logs” and “trace events.” It gives us one
    instrumentation API for logs and trace events (via `tracing::event!`),
    and subscribers decide what gets treated as logs, trace events, or both.
    
    Before this change, our OTEL trace layer was effectively attached to the
    general tracing stream, which meant turning on `trace_exporter` could
    pick up content-rich events that were originally written with logging
    (and the `log_exporter`) in mind. That made it too easy for sensitive
    data to end up in exported traces by accident.
    
    ### Concrete example
    In `otel_manager.rs`, this `tracing::event!` call would be exported in
    both logs AND traces (as a trace event).
    ```
        pub fn user_prompt(&self, items: &[UserInput]) {
            let prompt = items
                .iter()
                .flat_map(|item| match item {
                    UserInput::Text { text, .. } => Some(text.as_str()),
                    _ => None,
                })
                .collect::<String>();
    
            let prompt_to_log = if self.metadata.log_user_prompts {
                prompt.as_str()
            } else {
                "[REDACTED]"
            };
    
            tracing::event!(
                tracing::Level::INFO,
                event.name = "codex.user_prompt",
                event.timestamp = %timestamp(),
                // ...
                prompt = %prompt_to_log,
            );
        }
    ```
    
    Instead of `tracing::event!`, we should now be using `log_event!` and
    `trace_event!` instead to more clearly indicate which sink (logs vs.
    traces) that event should be exported to.
    
    ### What changed
    This PR makes the log and trace export distinct instead of treating them
    as two sinks for the same data.
    
    On the provider side, OTEL logs and traces now have separate
    routing/filtering policy. The log exporter keeps receiving the existing
    `codex_otel` events, while trace export is limited to spans and trace
    events.
    
    On the event side, `OtelManager` now emits two flavors of telemetry
    where needed:
    - a log-only event with the current rich payloads
    - a tracing-safe event with summaries only
    
    It also has a convenience `log_and_trace_event!` macro for emitting to
    both logs and traces when it's safe to do so, as well as log- and
    trace-specific fields.
    
    That means prompts, tool args, tool output, account email, MCP metadata,
    and similar content stay in the log lane, while traces get the pieces
    that are actually useful for performance work: durations, counts, sizes,
    status, token counts, tool origin, and normalized error classes.
    
    This preserves current IT/security logging behavior while making it safe
    to turn on trace export for employees.
    
    ### Full list of things removed from trace export
    - raw user prompt text from `codex.user_prompt`
    - raw tool arguments and output from `codex.tool_result`
    - MCP server metadata from `codex.tool_result` (mcp_server,
    mcp_server_origin)
    - account identity fields like `user.email` and `user.account_id` from
    trace-safe OTEL events
    - `host.name` from trace resources
    - generic `codex.tool_decision` events from traces
    - generic `codex.sse_event` events from traces
    - the full ToolCall debug payload from the `handle_tool_call` span
    
    What traces now keep instead is mostly:
    - spans
    - trace-safe OTEL events
    - counts, lengths, durations, status, token counts, and tool origin
    summaries
  • core/protocol: add structured macOS additional permissions and merge them into sandbox execution (#13499)
    ## Summary
    - Introduce strongly-typed macOS additional permissions across
    protocol/core/app-server boundaries.
    - Merge additional permissions into effective sandbox execution,
    including macOS seatbelt profile extensions.
    - Expand docs, schema/tool definitions, UI rendering, and tests for
    `network`, `file_system`, and `macos` additional permissions.
  • add @plugin mentions (#13510)
    ## Note-- added plugin mentions via @, but that conflicts with file
    mentions
    
    depends and builds upon #13433.
    
    - introduces explicit `@plugin` mentions. this injects the plugin's mcp
    servers, app names, and skill name format into turn context as a dev
    message.
    - we do not yet have UI for these mentions, so we currently parse raw
    text (as opposed to skills and apps which have UI chips, autocomplete,
    etc.) this depends on a `plugins/list` app-server endpoint we can feed
    the UI with, which is upcoming
    - also annotate mcp and app tool descriptions with the plugin(s) they
    come from. this gives the model a first class way of understanding what
    tools come from which plugins, which will help implicit invocation.
    
    ### Tests
    Added and updated tests, unit and integration. Also confirmed locally a
    raw `@plugin` injects the dev message, and the model knows about its
    apps, mcps, and skills.
  • Harden js_repl emitImage to accept only data: URLs (#13507)
    ### Motivation
    
    - Prevent untrusted js_repl code from supplying arbitrary external URLs
    that the host would forward into model input and cause external fetches
    / data exfiltration. This change narrows the emitImage contract to safe,
    self-contained data URLs.
    
    ### Description
    
    - Kernel: added `normalizeEmitImageUrl` and enforce that string-valued
    `codex.emitImage(...)` inputs and `input_image`/content-item paths only
    accept non-empty `data:` URLs; byte-based paths still produce data URLs
    as before (`kernel.js`).
    - Host: added `validate_emitted_image_url` and check `EmitImage`
    requests before creating `FunctionCallOutputContentItem::InputImage`,
    returning an error to the kernel if the URL is not a `data:` URL
    (`mod.rs`).
    - Tests/docs: added a runtime test
    `js_repl_emit_image_rejects_non_data_url` to assert rejection of
    non-data URLs and updated user-facing docs/instruction text to state
    `data URL` support instead of generic direct image URLs (`mod.rs`,
    `docs/js_repl.md`, `project_doc.rs`).
    
    ### Testing
    
    - Ran `just fmt` in `codex-rs`; it completed successfully.
    - Added a runtime test (`cargo test -p codex-core
    js_repl_emit_image_rejects_non_data_url`) but executing the test in this
    environment failed due to a missing system dependency required by
    `codex-linux-sandbox` (the vendored `bubblewrap` build requires
    `libcap.pc` via `pkg-config`), so the test could not be run here.
    - Attempted a focused `cargo test` invocation with and without default
    features; both compile/test attempts were blocked by the same missing
    system `libcap` dependency in this environment.
    
    ------
    [Codex
    Task](https://chatgpt.com/codex/tasks/task_i_69a7837bce98832d91db92d5f76d6cbe)
  • feat: merge skill permission profiles into the turn sandbox for zsh-fork execs (#13496)
    ## Summary
    
    This changes the Unix shell escalation path for skill-matched
    executables to apply a skill's `PermissionProfile` as additive
    permissions on top of the existing turn/request sandbox policy.
    
    Previously, skill-matched executables compiled the skill permission
    profile into a standalone sandbox policy and executed against that
    replacement policy. Now they go through the same
    `additional_permissions` merge path used elsewhere in shell sandbox
    preparation.
    
    ## What Changed
    
    - Changed `skill_escalation_execution()` to return
    `EscalationPermissions::PermissionProfile(...)` for non-empty skill
    permission profiles.
    - Kept empty or missing skill permission profiles on the `TurnDefault`
    path.
    - Added tests covering the new additive skill-permission behavior.
    - Added inline comments in `prepare_escalated_exec()` clarifying the
    difference between additive permission merging and fully specified
    replacement sandbox policies.
    - Removed the now-unused skill permission compiler module after
    switching this path away from standalone compiled skill sandbox
    policies.
    
    ## Testing
    
    - Ran `just fmt` in `codex-rs`
    - Ran `cargo test -p codex-core`
    
    `cargo test -p codex-core` still hits an unrelated existing failure:
    `shell_snapshot::tests::snapshot_shell_does_not_inherit_stdin`
    
    ## Follow-up
    
    This change intentionally does not merge skill-specific macOS seatbelt
    profile extensions through the `additional_permissions` path yet.
    Filesystem and network permissions now follow the additive merge path,
    but seatbelt extension permissions still need separate handling in a
    follow-up PR.
  • Persist initialized js_repl bindings after failed cells (#13482)
    ## Summary
    
    - Change `js_repl` failed-cell persistence so later cells keep prior
    bindings plus only the current-cell bindings whose initialization
    definitely completed before the throw.
    - Preserve initialized lexical bindings across failed cells via
    module-namespace readability, including top-level destructuring that
    partially succeeds before a later throw.
    - Preserve hoisted `var` and `function` bindings only when execution
    clearly reached their declaration site, and preserve direct top-level
    pre-declaration `var` writes and updates through explicit write-site
    markers.
    - Preserve top-level `for...in` / `for...of` `var` bindings when the
    loop body executes at least once, using a first-iteration guard to avoid
    per-iteration bookkeeping overhead.
    - Keep prior module state intact across link-time failures and
    evaluation failures before the prelude runs, while still allowing failed
    cells that already recreated prior bindings to persist updates to those
    existing bindings.
    - Hide internal commit hooks from user `js_repl` code after the prelude
    aliases them, so snippets cannot spoof committed bindings by calling the
    raw `import.meta` hooks directly.
    - Add focused regression coverage for the supported failed-cell
    behaviors and the intentionally unsupported boundaries.
    - Update `js_repl` docs and generated instructions to describe the new,
    narrower failed-cell persistence model.
    
    ## Motivation
    
    We saw `js_repl` drop bindings that had already been initialized
    successfully when a later statement in the same cell threw, for example:
    
        const { context: liveContext, session } =
          await initializeGoogleSheetsLiveForTab(tab);
        // later statement throws
    
    That was surprising in practice because successful earlier work
    disappeared from the next cell.
    
    This change makes failed-cell persistence more useful without trying to
    model every possible partially executed JavaScript edge case. The
    resulting behavior is narrower and easier to reason about:
    
    - prior bindings are always preserved
    - lexical bindings persist when their initialization completed before
    the throw
    - hoisted `var` / `function` bindings persist only when execution
    clearly reached their declaration or a supported top-level `var` write
    site
    - failed cells that already recreated prior bindings can persist writes
    to those existing bindings even if they introduce no new bindings
    
    The detailed edge-case matrix stays in `docs/js_repl.md`. The
    model-facing `project_doc` guidance is intentionally shorter and focused
    on generation-relevant behavior.
    
    ## Supported Failed-Cell Behavior
    
    - Prior bindings remain available after a failed cell.
    - Initialized lexical bindings remain available after a failed cell.
    - Top-level destructuring like `const { a, b } = ...` preserves names
    whose initialization completed before a later throw.
    - Hoisted `function` bindings persist when execution reached the
    declaration statement before the throw.
    - Direct top-level pre-declaration `var` writes and updates persist, for
    example:
      - `x = 1`
      - `x += 1`
      - `x++`
    - short-circuiting logical assignments only persist when the write
    branch actually runs
    - Non-empty top-level `for...in` / `for...of` `var` loops persist their
    loop bindings.
    - Failed cells can persist updates to existing carried bindings after
    the prelude has run, even when the cell commits no new bindings.
    - Link failures and eval failures before the prelude do not poison
    `@prev`.
    
    ## Intentionally Unsupported Failed-Cell Cases
    
    - Hoisted function reads before the declaration, such as `foo(); ...;
    function foo() {}`
    - Aliasing or inference-based recovery from reads before declaration
    - Nested writes inside already-instrumented assignment RHS expressions
    - Destructuring-assignment recovery for hoisted `var`
    - Partial `var` destructuring recovery
    - Pre-declaration `undefined` reads for hoisted `var`
    - Empty top-level `for...in` / `for...of` loop vars
    - Nested or scope-sensitive pre-declaration `var` writes outside direct
    top-level expression statements
  • refactor: prepare unified exec for zsh-fork backend (#13392)
    ## Why
    
    `shell_zsh_fork` already provides stronger guarantees around which
    executables receive elevated permissions. To reuse that machinery from
    unified exec without pushing Unix-specific escalation details through
    generic runtime code, the escalation bootstrap and session lifetime
    handling need a cleaner boundary.
    
    That boundary also needs to be safe for long-lived sessions: when an
    intercepted shell session is closed or pruned, any in-flight approval
    workers and any already-approved escalated child they spawned must be
    torn down with the session, and the inherited escalation socket must not
    leak into unrelated subprocesses.
    
    ## What Changed
    
    - Extracted a reusable `EscalationSession` and
    `EscalateServer::start_session(...)` in `shell-escalation` so callers
    can get the wrapper/socket env overlay and keep the escalation server
    alive without immediately running a one-shot command.
    - Documented that `EscalationSession::env()` and
    `ShellCommandExecutor::run(...)` exchange only that env overlay, which
    callers must merge into their own base shell environment.
    - Clarified the prepared-exec helper boundary in `core` by naming the
    new helper APIs around `ExecRequest`, while keeping the legacy
    `execute_env(...)` entrypoints as thin compatibility wrappers for
    existing callers that still use the older naming.
    - Added a small post-spawn hook on the prepared execution path so the
    parent copy of the inheritable escalation socket is closed immediately
    after both the existing one-shot shell-command spawn and the
    unified-exec spawn.
    - Made session teardown explicit with session-scoped cancellation:
    dropping an `EscalationSession` or canceling its parent request now
    stops intercept workers, and the server-spawned escalated child uses
    `kill_on_drop(true)` so teardown cannot orphan an already-approved
    child.
    - Added `UnifiedExecBackendConfig` plumbing through `ToolsConfig`, a
    `shell::zsh_fork_backend` facade, and an opaque unified-exec
    spawn-lifecycle hook so unified exec can prepare a wrapped `zsh -c/-lc`
    request without storing `EscalationSession` directly in generic
    process/runtime code.
    - Kept the existing `shell_command` zsh-fork behavior intact on top of
    the new bootstrap path. Tool selection is unchanged in this PR: when
    `shell_zsh_fork` is enabled, `ShellCommand` still wins over
    `exec_command`.
    
    ## Verification
    
    - `cargo test -p codex-shell-escalation`
      - includes coverage for `start_session_exposes_wrapper_env_overlay`
      - includes coverage for `exec_closes_parent_socket_after_shell_spawn`
    - includes coverage for
    `dropping_session_aborts_intercept_workers_and_kills_spawned_child`
    - `cargo test -p codex-core
    shell_zsh_fork_prefers_shell_command_over_unified_exec`
    - `cargo test -p codex-core --test all
    shell_zsh_fork_prompts_for_skill_script_execution`
    
    
    ---
    [//]: # (BEGIN SAPLING FOOTER)
    Stack created with [Sapling](https://sapling-scm.com). Best reviewed
    with [ReviewStack](https://reviewstack.dev/openai/codex/pull/13392).
    * #13432
    * __->__ #13392
  • chore: add web_search_tool_type for image support (#13538)
    add `web_search_tool_type` on model_info that can be populated from
    backend. will be used to filter which models can use `web_search` with
    images and which cant.
    
    added small unit test.
  • [js_repl] Support local ESM file imports (#13437)
    ## Summary
    - add `js_repl` support for dynamic imports of relative and absolute
    local ESM `.js` / `.mjs` files
    - keep bare package imports on the native Node path and resolved from
    REPL-global search roots (`CODEX_JS_REPL_NODE_MODULE_DIRS`, then `cwd`),
    even when they originate from imported local files
    - restrict static imports inside imported local files to other local
    relative/absolute `.js` / `.mjs` files, and surface a clear error for
    unsupported top-level static imports in the REPL cell
    - run imported local files inside the REPL VM context so they can access
    `codex.tmpDir`, `codex.tool`, captured `console`, and Node-like
    `import.meta` helpers
    - reload local files between execs so later `await import("./file.js")`
    calls pick up edits and fixed failures, while preserving package/builtin
    caching and persistent top-level REPL bindings
    - make `import.meta.resolve()` self-consistent by allowing the returned
    `file://...` URLs to round-trip through `await import(...)`
    - update both public and injected `js_repl` docs to clarify the narrowed
    contract, including global bare-import resolution behavior for local
    absolute files
    
    ## Testing
    - `cargo test -p codex-core js_repl_`
    - built codex binary and verified behavior
    
    ---------
    
    Co-authored-by: Codex <noreply@openai.com>
  • feat: external artifacts builder (#13485)
    This PR reverts the built-in artifact render while a decision is being
    reached. No impact expected on any features
  • image-gen-core (#13290)
    Core tool-calling for image-gen, handles requesting and receiving logic
    for images using response API
  • config: enforce enterprise feature requirements (#13388)
    ## Why
    
    Enterprises can already constrain approvals, sandboxing, and web search
    through `requirements.toml` and MDM, but feature flags were still only
    configurable as managed defaults. That meant an enterprise could suggest
    feature values, but it could not actually pin them.
    
    This change closes that gap and makes enterprise feature requirements
    behave like the other constrained settings. The effective feature set
    now stays consistent with enterprise requirements during config load,
    when config writes are validated, and when runtime code mutates feature
    flags later in the session.
    
    It also tightens the runtime API for managed features. `ManagedFeatures`
    now follows the same constraint-oriented shape as `Constrained<T>`
    instead of exposing panic-prone mutation helpers, and production code
    can no longer construct it through an unconstrained `From<Features>`
    path.
    
    The PR also hardens the `compact_resume_fork` integration coverage on
    Windows. After the feature-management changes,
    `compact_resume_after_second_compaction_preserves_history` was
    overflowing the libtest/Tokio thread stacks on Windows, so the test now
    uses an explicit larger-stack harness as a pragmatic mitigation. That
    may not be the ideal root-cause fix, and it merits a parallel
    investigation into whether part of the async future chain should be
    boxed to reduce stack pressure instead.
    
    ## What Changed
    
    Enterprises can now pin feature values in `requirements.toml` with the
    requirements-side `features` table:
    
    ```toml
    [features]
    personality = true
    unified_exec = false
    ```
    
    Only canonical feature keys are allowed in the requirements `features`
    table; omitted keys remain unconstrained.
    
    - Added a requirements-side pinned feature map to
    `ConfigRequirementsToml`, threaded it through source-preserving
    requirements merge and normalization in `codex-config`, and made the
    TOML surface use `[features]` (while still accepting legacy
    `[feature_requirements]` for compatibility).
    - Exposed `featureRequirements` from `configRequirements/read`,
    regenerated the JSON/TypeScript schema artifacts, and updated the
    app-server README.
    - Wrapped the effective feature set in `ManagedFeatures`, backed by
    `ConstrainedWithSource<Features>`, and changed its API to mirror
    `Constrained<T>`: `can_set(...)`, `set(...) -> ConstraintResult<()>`,
    and result-returning `enable` / `disable` / `set_enabled` helpers.
    - Removed the legacy-usage and bulk-map passthroughs from
    `ManagedFeatures`; callers that need those behaviors now mutate a plain
    `Features` value and reapply it through `set(...)`, so the constrained
    wrapper remains the enforcement boundary.
    - Removed the production loophole for constructing unconstrained
    `ManagedFeatures`. Non-test code now creates it through the configured
    feature-loading path, and `impl From<Features> for ManagedFeatures` is
    restricted to `#[cfg(test)]`.
    - Rejected legacy feature aliases in enterprise feature requirements,
    and return a load error when a pinned combination cannot survive
    dependency normalization.
    - Validated config writes against enterprise feature requirements before
    persisting changes, including explicit conflicting writes and
    profile-specific feature states that normalize into invalid
    combinations.
    - Updated runtime and TUI feature-toggle paths to use the constrained
    setter API and to persist or apply the effective post-constraint value
    rather than the requested value.
    - Updated the `core_test_support` Bazel target to include the bundled
    core model-catalog fixtures in its runtime data, so helper code that
    resolves `core/models.json` through runfiles works in remote Bazel test
    environments.
    - Renamed the core config test coverage to emphasize that effective
    feature values are normalized at runtime, while conflicting persisted
    config writes are rejected.
    - Ran `compact_resume_after_second_compaction_preserves_history` inside
    an explicit 8 MiB test thread and Tokio runtime worker stack, following
    the existing larger-stack integration-test pattern, to keep the Windows
    `compact_resume_fork` test slice from aborting while a parallel
    investigation continues into whether some of the underlying async
    futures should be boxed.
    
    ## Verification
    
    - `cargo test -p codex-config`
    - `cargo test -p codex-core feature_requirements_ -- --nocapture`
    - `cargo test -p codex-core
    load_requirements_toml_produces_expected_constraints -- --nocapture`
    - `cargo test -p codex-core
    compact_resume_after_second_compaction_preserves_history -- --nocapture`
    - `cargo test -p codex-core compact_resume_fork -- --nocapture`
    - Re-ran the built `codex-core` `tests/all` binary with
    `RUST_MIN_STACK=262144` for
    `compact_resume_after_second_compaction_preserves_history` to confirm
    the explicit-stack harness fixes the deterministic low-stack repro.
    - `cargo test -p codex-core`
    - This still fails locally in unrelated integration areas that expect
    the `codex` / `test_stdio_server` binaries or hit existing `search_tool`
    wiremock mismatches.
    
    ## Docs
    
    `developers.openai.com/codex` should document the requirements-side
    `[features]` table for enterprise and MDM-managed configuration,
    including that it only accepts canonical feature keys and that
    conflicting config writes are rejected.
  • Make js_repl image output controllable (#13331)
    ## Summary
    
    Instead of always adding inner function call outputs to the model
    context, let js code decide which ones to return.
    
    - Stop auto-hoisting nested tool outputs from `codex.tool(...)` into the
    outer `js_repl` function output.
    - Keep `codex.tool(...)` return values unchanged as structured JS
    objects.
    - Add `codex.emitImage(...)` as the explicit path for attaching an image
    to the outer `js_repl` function output.
    - Support emitting from a direct image URL, a single `input_image` item,
    an explicit `{ bytes, mimeType }` object, or a raw tool response object
    containing exactly one image.
    - Preserve existing `view_image` original-resolution behavior when JS
    emits the raw `view_image` tool result.
    - Suppress the special `ViewImageToolCall` event for `js_repl`-sourced
    `view_image` calls so nested inspection stays side-effect free until JS
    explicitly emits.
    - Update the `js_repl` docs and generated project instructions with both
    recommended patterns:
      - `await codex.emitImage(codex.tool("view_image", { path }))`
    - `await codex.emitImage({ bytes: await page.screenshot({ type: "jpeg",
    quality: 85 }), mimeType: "image/jpeg" })`
    
    #### [git stack](https://github.com/magus/git-stack-cli)
    -  `1` https://github.com/openai/codex/pull/13050
    - 👉 `2` https://github.com/openai/codex/pull/13331
    -  `3` https://github.com/openai/codex/pull/13049
  • Add under-development original-resolution view_image support (#13050)
    ## Summary
    
    Add original-resolution support for `view_image` behind the
    under-development `view_image_original_resolution` feature flag.
    
    When the flag is enabled and the target model is `gpt-5.3-codex` or
    newer, `view_image` now preserves original PNG/JPEG/WebP bytes and sends
    `detail: "original"` to the Responses API instead of using the legacy
    resize/compress path.
    
    ## What changed
    
    - Added `view_image_original_resolution` as an under-development feature
    flag.
    - Added `ImageDetail` to the protocol models and support for serializing
    `detail: "original"` on tool-returned images.
    - Added `PromptImageMode::Original` to `codex-utils-image`.
      - Preserves original PNG/JPEG/WebP bytes.
      - Keeps legacy behavior for the resize path.
    - Updated `view_image` to:
    - use the shared `local_image_content_items_with_label_number(...)`
    helper in both code paths
      - select original-resolution mode only when:
        - the feature flag is enabled, and
        - the model slug parses as `gpt-5.3-codex` or newer
    - Kept local user image attachments on the existing resize path; this
    change is specific to `view_image`.
    - Updated history/image accounting so only `detail: "original"` images
    use the docs-based GPT-5 image cost calculation; legacy images still use
    the old fixed estimate.
    - Added JS REPL guidance, gated on the same feature flag, to prefer JPEG
    at 85% quality unless lossless is required, while still allowing other
    formats when explicitly requested.
    - Updated tests and helper code that construct
    `FunctionCallOutputContentItem::InputImage` to carry the new `detail`
    field.
    
    ## Behavior
    
    ### Feature off
    - `view_image` keeps the existing resize/re-encode behavior.
    - History estimation keeps the existing fixed-cost heuristic.
    
    ### Feature on + `gpt-5.3-codex+`
    - `view_image` sends original-resolution images with `detail:
    "original"`.
    - PNG/JPEG/WebP source bytes are preserved when possible.
    - History estimation uses the GPT-5 docs-based image-cost calculation
    for those `detail: "original"` images.
    
    
    #### [git stack](https://github.com/magus/git-stack-cli)
    - 👉 `1` https://github.com/openai/codex/pull/13050
    -  `2` https://github.com/openai/codex/pull/13331
    -  `3` https://github.com/openai/codex/pull/13049
  • feat: pres artifact part 5 (#13355)
    Mostly written by Codex
  • feat: presentation artifact p1 (#13341)
    Part 1 of presentation tool artifact
  • fix: agent when profile (#13235)
    Co-authored-by: Josh McKinney <joshka@openai.com>
    Co-authored-by: Codex <noreply@openai.com>
  • chore: remove SkillMetadata.permissions and derive skill sandboxing from permission_profile (#13061)
    ## Summary
    
    This change removes the compiled permissions field from skill metadata
    and keeps permission_profile as the single source of truth.
    
    Skill loading no longer compiles skill permissions eagerly. Instead, the
    zsh-fork skill escalation path compiles `skill.permission_profile` when
    it needs to determine the sandbox to apply for a skill script.
    
      ## Behavior change
    
      For skills that declare:
    ```
      permissions: {}
    ```
    we now treat that the same as having no skill permissions override,
    instead of creating and using a default readonly sandbox. This change
    makes the behavior more intuitive:
    
      - only non-empty skill permission profiles affect sandboxing
    - omitting permissions and writing permissions: {} now mean the same
    thing
    - skill metadata keeps a single permissions representation instead of
    storing derived state too
    
    Overall, this makes skill sandbox behavior easier to understand and more
    predictable.
  • core: adopt host_executable() rules in zsh-fork (#13046)
    ## Why
    
    [#12964](https://github.com/openai/codex/pull/12964) added
    `host_executable()` support to `codex-execpolicy`, but the zsh-fork
    interception path in `unix_escalation.rs` was still evaluating commands
    with the default exact-token matcher.
    
    That meant an intercepted absolute executable such as `/usr/bin/git
    status` could still miss basename rules like `prefix_rule(pattern =
    ["git", "status"])`, even when the policy also defined a matching
    `host_executable(name = "git", ...)` entry.
    
    This PR adopts the new matching behavior in the zsh-fork runtime only.
    That keeps the rollout intentionally narrow: zsh-fork already requires
    explicit user opt-in, so it is a safer first caller to exercise the new
    `host_executable()` scheme before expanding it to other execpolicy call
    sites.
    
    It also brings zsh-fork back in line with the current `prefix_rule()`
    execution model. Until prefix rules can carry their own permission
    profiles, a matched `prefix_rule()` is expected to rerun the intercepted
    command unsandboxed on `allow`, or after the user accepts `prompt`,
    instead of merely continuing inside the inherited shell sandbox.
    
    ## What Changed
    
    - added `evaluate_intercepted_exec_policy()` in
    `core/src/tools/runtimes/shell/unix_escalation.rs` to centralize
    execpolicy evaluation for intercepted commands
    - switched intercepted direct execs in the zsh-fork path to
    `check_multiple_with_options(...)` with `MatchOptions {
    resolve_host_executables: true }`
    - added `commands_for_intercepted_exec_policy()` so zsh-fork policy
    evaluation works from intercepted `(program, argv)` data instead of
    reconstructing a synthetic command before matching
    - left shell-wrapper parsing intentionally disabled by default behind
    `ENABLE_INTERCEPTED_EXEC_POLICY_SHELL_WRAPPER_PARSING`, so
    path-sensitive matching relies on later direct exec interception rather
    than shell-script parsing
    - made matched `prefix_rule()` decisions rerun intercepted commands with
    `EscalationExecution::Unsandboxed`, while unmatched-command fallback
    keeps the existing sandbox-preserving behavior
    - extracted the zsh-fork test harness into
    `core/tests/common/zsh_fork.rs` so both the skill-focused and
    approval-focused integration suites can exercise the same runtime setup
    - limited this change to the intercepted zsh-fork path rather than
    changing every execpolicy caller at once
    - added runtime coverage in
    `core/src/tools/runtimes/shell/unix_escalation_tests.rs` for allowed and
    disallowed `host_executable()` mappings and the wrapper-parsing modes
    - added integration coverage in `core/tests/suite/approvals.rs` to
    verify a saved `prefix_rule(pattern=["touch"], decision="allow")` reruns
    under zsh-fork outside a restrictive `WorkspaceWrite` sandbox
    
    ---
    [//]: # (BEGIN SAPLING FOOTER)
    Stack created with [Sapling](https://sapling-scm.com). Best reviewed
    with [ReviewStack](https://reviewstack.dev/openai/codex/pull/13046).
    * #13065
    * __->__ #13046
  • fix: use AbsolutePathBuf for permission profile file roots (#12970)
    ## Why
    `PermissionProfile` should describe filesystem roots as absolute paths
    at the type level. Using `PathBuf` in `FileSystemPermissions` made the
    shared type too permissive and blurred together three different
    deserialization cases:
    
    - skill metadata in `agents/openai.yaml`, where relative paths should
    resolve against the skill directory
    - app-server API payloads, where callers should have to send absolute
    paths
    - local tool-call payloads for commands like `shell_command` and
    `exec_command`, where `additional_permissions.file_system` may
    legitimately be relative to the command `workdir`
    
    This change tightens the shared model without regressing the existing
    local command flow.
    
    ## What Changed
    - changed `protocol::models::FileSystemPermissions` and the app-server
    `AdditionalFileSystemPermissions` mirror to use `AbsolutePathBuf`
    - wrapped skill metadata deserialization in `AbsolutePathBufGuard`, so
    relative permission roots in `agents/openai.yaml` resolve against the
    containing skill directory
    - kept app-server/API deserialization strict, so relative
    `additionalPermissions.fileSystem.*` paths are rejected at the boundary
    - restored cwd/workdir-relative deserialization for local tool-call
    payloads by parsing `shell`, `shell_command`, and `exec_command`
    arguments under an `AbsolutePathBufGuard` rooted at the resolved command
    working directory
    - simplified runtime additional-permission normalization so it only
    canonicalizes and deduplicates absolute roots instead of trying to
    recover relative ones later
    - updated the app-server schema fixtures, `app-server/README.md`, and
    the affected transport/TUI tests to match the final behavior
  • notify: include client in legacy hook payload (#12968)
    ## Why
    
    The `notify` hook payload did not identify which Codex client started
    the turn. That meant downstream notification hooks could not distinguish
    between completions coming from the TUI and completions coming from
    app-server clients such as VS Code or Xcode. Now that the Codex App
    provides its own desktop notifications, it would be nice to be able to
    filter those out.
    
    This change adds that context without changing the existing payload
    shape for callers that do not know the client name, and keeps the new
    end-to-end test cross-platform.
    
    ## What changed
    
    - added an optional top-level `client` field to the legacy `notify` JSON
    payload
    - threaded that value through `core` and `hooks`; the internal session
    and turn state now carries it as `app_server_client_name`
    - set the field to `codex-tui` for TUI turns
    - captured `initialize.clientInfo.name` in the app server and applied it
    to subsequent turns before dispatching hooks
    - replaced the notify integration test hook with a `python3` script so
    the test does not rely on Unix shell permissions or `bash`
    - documented the new field in `docs/config.md`
    
    ## Testing
    
    - `cargo test -p codex-hooks`
    - `cargo test -p codex-tui`
    - `cargo test -p codex-app-server
    suite::v2::initialize::turn_start_notify_payload_includes_initialize_client_name
    -- --exact --nocapture`
    - `cargo test -p codex-core` (`src/lib.rs` passed; `core/tests/all.rs`
    still has unrelated existing failures in this environment)
    
    ## Docs
    
    The public config reference on `developers.openai.com/codex` should
    mention that the legacy `notify` payload may include a top-level
    `client` field. The TUI reports `codex-tui`, and the app server reports
    `initialize.clientInfo.name` when it is available.