33 Commits

  • apply-patch: carry paths as PathUri (#28854)
    ## Why
    
    Allows the model to edit files that are hosted on a different OS than
    where app-server is running.
    
    ## What
    
    * Use `PathUri` for apply_patch-internal data structures
    * Limit `PathUri` -> `AbsolutePathBuf` conversion to cases where the
    inferred path convention matches the host OS, allows requiring valid
    paths to pass to perms check
    * Adds `PathConvention::path_segments()` for iterating over path
    segments regardless of OS
    * Handle cross-platform relative paths in path filename parsing for
    sniffing a shell
    * Ensure we can apply patches in the wine e2e test
  • Add hidden Windows sandbox wrapper entrypoint (#28358)
    ## Why
    
    This is the second PR in the Windows fs-helper sandbox stack. The
    fs-helper path needs a Windows sandbox launcher that has the same
    argv-shaped contract as macOS `sandbox-exec` and `codex-linux-sandbox`,
    but this PR only introduces that hidden launcher. It does not route
    fs-helper through it yet.
    
    The hidden launcher still needs to be policy-complete before later
    direct-spawn callers use it. In particular, it has to carry the same
    Windows sandbox policy details that the existing spawn paths already
    understand: proxy enforcement, read/write root overrides, and
    deny-read/deny-write overrides.
    
    ## What Changed
    
    - Added the hidden `codex.exe --run-as-windows-sandbox` arg1 dispatch
    path.
    - Added `windows-sandbox-rs/src/wrapper.rs`, which parses the wrapper
    argv, launches the requested command through the shared Windows sandbox
    session runner from PR1, and forwards stdio.
    - Added `create_windows_sandbox_command_args_for_permission_profile()`
    so later direct-spawn callers can build the wrapper argv consistently.
    - Made the wrapper argv round-trip the full Windows sandbox policy
    surface it needs later: workspace roots, environment, permission
    profile, sandbox level, private desktop, proxy enforcement, read/write
    root overrides, and deny-read/deny-write overrides.
    - Carried `proxy_enforced` through the shared Windows session request so
    proxy-managed executions continue to use the offline/elevated sandbox
    identity.
    - Added wrapper argument round-trip coverage for the full policy fields.
    
    ## Verification
    
    - `just test -p codex-windows-sandbox windows_wrapper_args_round_trip`
    - `just test -p codex-arg0`
    - `just test -p codex-core exec::tests::windows_`
    - `just fix -p codex-windows-sandbox -p codex-core -p codex-cli`
    
    Local note: the full `just fmt` command still fails on this workstation
    in non-Rust formatter setup (`uv` cache access denied and missing
    `dotslash`/buildifier), but the Rust formatter phase completed.
  • cli: add package path from install context (#26189)
    ## Why
    
    Codex package installs include helper binaries in `codex-path`, such as
    the bundled `rg`. Package-layout launches should add that directory
    before user commands run, but standalone launches were missing it while
    npm launches only worked because `codex.js` had its own legacy `PATH`
    rewrite. That made npm and standalone package behavior diverge.
    
    Shell snapshot restoration can also reset `PATH` after runtime setup.
    Any package-owned `PATH` prepend has to be recorded as an explicit
    runtime override so shells, unified exec, and user-shell commands keep
    access to `codex-path` after a snapshot is sourced.
    
    ## Repro
    
    Before this change, a curl-installed package could contain `rg` under
    `codex-path` but still fail to put it on `PATH`:
    
    ```shell
    mkdir /tmp/test-codex-curl
    curl -fsSL https://chatgpt.com/codex/install.sh \
      | CODEX_HOME=/tmp/test-codex-curl CODEX_NON_INTERACTIVE=1 sh
    /tmp/test-codex-curl/packages/standalone/current/bin/codex exec \
      --skip-git-repo-check 'print `which -a rg`'
    find /tmp/test-codex-curl -name rg
    ```
    
    The `which -a rg` output omitted the packaged helper even though `find`
    showed it under
    `/tmp/test-codex-curl/packages/standalone/releases/.../codex-path/rg`.
    
    The npm install path behaved differently only because
    `codex-cli/bin/codex.js` had legacy `PATH` rewriting:
    
    ```shell
    mkdir /tmp/test-codex-npm
    cd /tmp/test-codex-npm
    npm install @openai/codex
    ./node_modules/.bin/codex exec --skip-git-repo-check 'print `which -a rg`'
    ```
    
    That printed the npm package's `vendor/<target>/codex-path/rg` first.
    This PR moves that behavior into Rust-side package launch setup so
    curl/standalone and npm/bun launches agree without JS rewriting `PATH`.
    
    ## What Changed
    
    - `codex-rs/arg0` now uses
    `InstallContext::current().package_layout.path_dir` to prepend the
    package helper directory before any threads are created.
    - Package helper `PATH` setup is independent from the temporary arg0
    alias setup, so `codex-path` is still added even if CODEX_HOME tempdir,
    lock, or symlink setup fails.
    - `codex-rs/install-context` detects the canonical package layout we
    ship: `bin/`, `codex-resources/`, and `codex-path/` next to
    `codex-package.json`.
    - Shell, local unified exec, and user-shell runtimes now record package
    `codex-path` prepends in `explicit_env_overrides`, matching the existing
    zsh-fork behavior so shell snapshots cannot restore over the package
    helper path.
    - Remote unified exec requests do not receive the local app-server
    package path overlay.
    - `codex-cli/bin/codex.js` no longer computes or overrides `PATH`; it
    only locates the native binary in the canonical package layout and
    passes npm/bun management metadata.
    - Added regression tests for `PATH` ordering, package layout detection,
    and shell snapshot preservation of package path prepends.
    
    ## Verification
    
    - `node --check codex-cli/bin/codex.js`
    - `just test -p codex-install-context -p codex-arg0`
    - `just test -p codex-core
    user_shell_snapshot_preserves_package_path_prepend`
    - `just test -p codex-core tools::runtimes::tests`
    - `just bazel-lock-update`
    - `just bazel-lock-check`
    - `just fix -p codex-install-context -p codex-arg0 -p codex-core`
  • Run Codex async main on a sized stack (#25847)
    ## Why
    
    `Runtime::block_on` executes the top-level future on the caller's OS
    thread, not on one of Tokio's worker threads. That matters for the
    interactive CLI because the Tokio runtime already configures larger
    worker stacks, while the process main thread can still have a smaller
    platform default stack.
    
    This showed up as a `/clear` crash on macOS: starting a fresh TUI thread
    reloads config, and the stack-heavy TOML deserialization path can
    overflow before the new session is actually started.
    
    ## What Changed
    
    - Run the regular `arg0_dispatch_or_else` async entrypoint on a named
    `codex-main` thread.
    - Give that thread the same `TOKIO_WORKER_STACK_SIZE_BYTES` stack budget
    already used for Tokio worker threads.
    - Keep `Arg0DispatchPaths` and the arg0 alias guard lifetime behavior
    the same.
    - Resume panics from the spawned main thread so panic behavior is
    preserved.
    
    ## Verification
    
    - `cargo check -p codex-cli` currently fails because the top-level
    CLI/TUI future is not `Send` under the new thread boundary.
  • Disable empty Cargo test targets (#21584)
    ## Summary
    
    `cargo test` has entails both running standard Rust tests and doctests.
    It turns out that the doctest discovery is fairly slow, and it's a cost
    you pay even for crates that don't include any doctests.
    
    This PR disables doctests with `doctest = false` for crates that lack
    any doctests.
    
    For the collection of crates below, this speeds up test execution by
    >4x.
    
    E.g., before this PR:
    
    ```
    Benchmark 1: cargo test     -p codex-utils-absolute-path     -p codex-utils-cache     -p codex-utils-cli     -p codex-utils-home-dir     -p codex-utils-output-truncation     -p codex-utils-path     -p codex-utils-string     -p codex-utils-template     -p codex-utils-elapsed     -p codex-utils-json-to-toml
      Time (mean ± σ):      1.849 s ±  4.455 s    [User: 0.752 s, System: 1.367 s]
      Range (min … max):    0.418 s … 14.529 s    10 runs
    ```
    
    And after:
    
    ```
    Benchmark 1: cargo test     -p codex-utils-absolute-path     -p codex-utils-cache     -p codex-utils-cli     -p codex-utils-home-dir     -p codex-utils-output-truncation     -p codex-utils-path     -p codex-utils-string     -p codex-utils-template     -p codex-utils-elapsed     -p codex-utils-json-to-toml
      Time (mean ± σ):     428.6 ms ±   6.9 ms    [User: 187.7 ms, System: 219.7 ms]
      Range (min … max):   418.0 ms … 436.8 ms    10 runs
    ```
    
    For a single crate, with >2x speedup, before:
    
    ```
    Benchmark 1: cargo test -p codex-utils-string
      Time (mean ± σ):     491.1 ms ±   9.0 ms    [User: 229.8 ms, System: 234.9 ms]
      Range (min … max):   480.9 ms … 512.0 ms    10 runs
    ```
    
    And after:
    
    ```
    Benchmark 1: cargo test -p codex-utils-string
      Time (mean ± σ):     213.9 ms ±   4.3 ms    [User: 112.8 ms, System: 84.0 ms]
      Range (min … max):   206.8 ms … 221.0 ms    13 runs
    ```
    
    Co-authored-by: Codex <noreply@openai.com>
  • Make turn diff tracking operation backed (#21180)
    ## Summary
    - replace filesystem-based turn diff tracking with an operation-backed
    accumulator
    - preserve enough verified apply_patch state to render move-overwrite
    cases correctly
    - keep the turn/diff/updated contract intact while removing remote-only
    turn-diff test skips
    
    This takes the assumption that no 3P services rely on the output format
    of `apply_patch`
    
    ## Why
    For the CCA file system isolation push
    
    ---------
    
    Co-authored-by: Codex <noreply@openai.com>
  • arg0: keep dispatch aliases alive during async main (#18999)
    ## Why
    
    The Ubuntu GNU remote Cargo run has been regularly failing sandboxed
    `suite::remote_env` filesystem tests with `No such file or directory`,
    while the same cases pass under Bazel. The Cargo remote-env setup starts
    `target/debug/codex exec-server` inside Docker via
    `scripts/test-remote-env.sh`. That CLI builds `codex-linux-sandbox` and
    other arg0 helper aliases in a temporary directory, then passes those
    alias paths into the exec-server runtime.
    
    `arg0_dispatch_or_else` constructed `Arg0DispatchPaths` from that
    temporary alias guard, but then awaited the async CLI entry point
    without otherwise keeping the guard live. That allowed the guard to be
    dropped while the exec-server was still running, removing the helper
    alias directory. Later sandboxed filesystem calls tried to spawn the
    now-deleted `codex-linux-sandbox` path and surfaced as `ENOENT`.
    
    The relevant distinction I found is that `core/tests/common` stores the
    result of `arg0_dispatch()` in a process-lifetime
    `OnceLock<Option<Arg0PathEntryGuard>>` for test binaries. The Cargo
    remote-env setup exercises a real `codex exec-server` process instead,
    so it depends on the normal CLI lifetime behavior fixed here.
    
    ## What Changed
    
    - Keep the arg0 tempdir guard alive until `main_fn(paths).await`
    completes.
    - Keep the helper on the real `arg0_dispatch()` shape, where alias setup
    can fail and return `None` in production.
    - Add a regression test that uses an explicit guard, yields once, and
    verifies the generated helper alias path still exists while the async
    entry point is running.
    
    ## Verification
    
    - `cargo test -p codex-arg0`
    - `just argument-comment-lint -p codex-arg0`
    - `just fix -p codex-arg0`
  • Route apply_patch through the environment filesystem (#17674)
    ## Summary
    - route apply_patch runtime execution through the selected Environment
    filesystem instead of the local self-exec path
    - keep the standalone apply_patch command surface intact while restoring
    its launcher/test/docs contract
    - add focused apply_patch filesystem sandbox regression coverage
    
    ## Validation
    - remote devbox Bazel run in progress
    - passed: //codex-rs/apply-patch:apply-patch-unit-tests
    --test_filter=test_read_file_utf8_with_context_reports_invalid_utf8
    - in progress / follow-up: focused core and exec Bazel test slices on
    dev
    
    ## Follow-up under review
    - remote pre-verification and approval/retry behavior still need
    explicit scrutiny for delete/update flows
    - runtime sandbox-denial classification may need a tighter assertion
    path than rendered stderr matching
    
    ---------
    
    Co-authored-by: Codex <noreply@openai.com>
  • Run exec-server fs operations through sandbox helper (#17294)
    ## Summary
    - run exec-server filesystem RPCs requiring sandboxing through a
    `codex-fs` arg0 helper over stdin/stdout
    - keep direct local filesystem execution for `DangerFullAccess` and
    external sandbox policies
    - remove the standalone exec-server binary path in favor of top-level
    arg0 dispatch/runtime paths
    - add sandbox escape regression coverage for local and remote filesystem
    paths
    
    ## Validation
    - `just fmt`
    - `git diff --check`
    - remote devbox: `cd codex-rs && bazel test --bes_backend=
    --bes_results_url= //codex-rs/exec-server:all` (6/6 passed)
    
    ---------
    
    Co-authored-by: Codex <noreply@openai.com>
  • [codex] Migrate apply_patch to executor filesystem (#17027)
    - Migrate apply-patch verification and application internals to use the
    async `ExecutorFileSystem` abstraction from `exec-server`.
    - Convert apply-patch `cwd` handling to `AbsolutePathBuf` through the
    verifier/parser/handler boundary.
    
    Doesn't change how the tool itself works.
  • fix: fix old system bubblewrap compatibility without falling back to vendored bwrap (#15693)
    Fixes #15283.
    
    ## Summary
    Older system bubblewrap builds reject `--argv0`, which makes our Linux
    sandbox fail before the helper can re-exec. This PR keeps using system
    `/usr/bin/bwrap` whenever it exists and only falls back to vendored
    bwrap when the system binary is missing. That matters on stricter
    AppArmor hosts, where the distro bwrap package also provides the policy
    setup needed for user namespaces.
    
    For old system bwrap, we avoid `--argv0` instead of switching binaries:
    - pass the sandbox helper a full-path `argv0`,
    - keep the existing `current_exe() + --argv0` path when the selected
    launcher supports it,
    - otherwise omit `--argv0` and re-exec through the helper's own
    `argv[0]` path, whose basename still dispatches as
    `codex-linux-sandbox`.
    
    Also updates the launcher/warning tests and docs so they match the new
    behavior: present-but-old system bwrap uses the compatibility path, and
    only absent system bwrap falls back to vendored.
    
    ### Validation
    
    1. Install Ubuntu 20.04 in a VM
    2. Compile codex and run without bubblewrap installed - see a warning
    about falling back to the vendored bwrap
    3. Install bwrap and verify version is 0.4.0 without `argv0` support
    4. run codex and use apply_patch tool without errors
    
    <img width="802" height="631" alt="Screenshot 2026-03-25 at 11 48 36 PM"
    src="https://github.com/user-attachments/assets/77248a29-aa38-4d7c-9833-496ec6a458b8"
    />
    <img width="807" height="634" alt="Screenshot 2026-03-25 at 11 47 32 PM"
    src="https://github.com/user-attachments/assets/5af8b850-a466-489b-95a6-455b76b5050f"
    />
    <img width="812" height="635" alt="Screenshot 2026-03-25 at 11 45 45 PM"
    src="https://github.com/user-attachments/assets/438074f0-8435-4274-a667-332efdd5cb57"
    />
    <img width="801" height="623" alt="Screenshot 2026-03-25 at 11 43 56 PM"
    src="https://github.com/user-attachments/assets/0dc8d3f5-e8cf-4218-b4b4-a4f7d9bf02e3"
    />
    
    ---------
    
    Co-authored-by: Michael Bolin <mbolin@openai.com>
  • feat: pass helper executable paths via Arg0DispatchPaths (#12719)
    ## Why
    
    `codex-rs/core/src/tools/runtimes/shell/unix_escalation.rs` previously
    located `codex-execve-wrapper` by scanning `PATH` and sibling
    directories. That lookup is brittle and can select the wrong binary when
    the runtime environment differs from startup assumptions.
    
    We already pass `codex-linux-sandbox` from `codex-arg0`;
    `codex-execve-wrapper` should use the same startup-driven path plumbing.
    
    ## What changed
    
    - Introduced `Arg0DispatchPaths` in `codex-arg0` to carry both helper
    executable paths:
      - `codex_linux_sandbox_exe`
      - `main_execve_wrapper_exe`
    - Updated `arg0_dispatch_or_else()` to pass `Arg0DispatchPaths` to
    top-level binaries and preserve helper paths created in
    `prepend_path_entry_for_codex_aliases()`.
    - Threaded `Arg0DispatchPaths` through entrypoints in `cli`, `exec`,
    `tui`, `app-server`, and `mcp-server`.
    - Added `main_execve_wrapper_exe` to core configuration plumbing
    (`Config`, `ConfigOverrides`, and `SessionServices`).
    - Updated zsh-fork shell escalation to consume the configured
    `main_execve_wrapper_exe` and removed path-sniffing fallback logic.
    - Updated app-server config reload paths so reloaded configs keep the
    same startup-provided helper executable paths.
    
    ## References
    
    - [`Arg0DispatchPaths`
    definition](https://github.com/openai/codex/blob/e355b43d5c2a771f045296a6deae10d7c9c36ec6/codex-rs/arg0/src/lib.rs#L20-L24)
    - [`arg0_dispatch_or_else()` forwarding both
    paths](https://github.com/openai/codex/blob/e355b43d5c2a771f045296a6deae10d7c9c36ec6/codex-rs/arg0/src/lib.rs#L145-L176)
    - [zsh-fork escalation using configured wrapper
    path](https://github.com/openai/codex/blob/e355b43d5c2a771f045296a6deae10d7c9c36ec6/codex-rs/core/src/tools/runtimes/shell/unix_escalation.rs#L109-L150)
    
    ## Testing
    
    - `cargo check -p codex-arg0 -p codex-core -p codex-exec -p codex-tui -p
    codex-mcp-server -p codex-app-server`
    - `cargo test -p codex-arg0`
    - `cargo test -p codex-core tools::runtimes::shell::unix_escalation:: --
    --nocapture`
  • feat: run zsh fork shell tool via shell-escalation (#12649)
    ## Why
    
    This PR switches the `shell_command` zsh-fork path over to
    `codex-shell-escalation` so the new shell tool can use the shared
    exec-wrapper/escalation protocol instead of the `zsh_exec_bridge`
    implementation that was introduced in
    https://github.com/openai/codex/pull/12052. `zsh_exec_bridge` relied on
    UNIX domain sockets, which is not as tamper-proof as the FD-based
    approach in `codex-shell-escalation`.
    
    ## What Changed
    
    - Added a Unix zsh-fork runtime adapter in `core`
    (`core/src/tools/runtimes/shell/unix_escalation.rs`) that:
    - runs zsh-fork commands through
    `codex_shell_escalation::run_escalate_server`
      - bridges exec-policy / approval decisions into `ShellActionProvider`
    - executes escalated commands via a `ShellCommandExecutor` that calls
    `process_exec_tool_call`
    - Updated `ShellRuntime` / `ShellCommandHandler` / tool spec wiring to
    select a `shell_command` backend (`classic` vs `zsh-fork`) while leaving
    the generic `shell` tool path unchanged.
    - Removed the `zsh_exec_bridge`-based session service and deleted
    `core/src/zsh_exec_bridge/mod.rs`.
    - Moved exec-wrapper entrypoint dispatch to `arg0` by handling the
    `codex-execve-wrapper` arg0 alias there, and removed the old
    `codex_core::maybe_run_zsh_exec_wrapper_mode()` hooks from `cli` and
    `app-server` mains.
    - Added the needed `codex-shell-escalation` dependencies for `core` and
    `arg0`.
    
    ## Tests
    
    - `cargo test -p codex-core
    shell_zsh_fork_prefers_shell_command_over_unified_exec`
    - `cargo test -p codex-app-server turn_start_shell_zsh_fork --
    --nocapture`
    - verifies zsh-fork command execution and approval flows through the new
    backend
    - includes subcommand approve/decline coverage using the shared zsh
    DotSlash fixture in `app-server/tests/suite/zsh`
    - To test manually, I added the following to `~/.codex/config.toml`:
    
    ```toml
    zsh_path = "/Users/mbolin/code/codex3/codex-rs/app-server/tests/suite/zsh"
    
    [features]
    shell_zsh_fork = true
    ```
    
    Then I ran `just c` to run the dev build of Codex with these changes and
    sent it the message:
    
    ```
    run `echo $0`
    ```
    
    And it replied with:
    
    ```
      echo $0 printed:
    
      /Users/mbolin/code/codex3/codex-rs/app-server/tests/suite/zsh
    
      In this tool context, $0 reflects the script path used to invoke the shell, not just zsh.
    ```
    
    so the tool appears to be wired up correctly.
    
    ## Notes
    
    - The zsh subcommand-decline integration test now uses `rm` under a
    `WorkspaceWrite` sandbox. The previous `/usr/bin/true` scenario is
    auto-allowed by the new `shell-escalation` policy path, which no longer
    produces subcommand approval prompts.
  • fix: codex-arg0 no longer depends on codex-core (#12434)
    ## Why
    
    `codex-rs/arg0` only needed two things from `codex-core`:
    
    - the `find_codex_home()` wrapper
    - the special argv flag used for the internal `apply_patch`
    self-invocation path
    
    That made `codex-arg0` depend on `codex-core` for a very small surface
    area. This change removes that dependency edge and moves the shared
    `apply_patch` invocation flag to a more natural boundary
    (`codex-apply-patch`) while keeping the contract explicitly documented.
    
    ## What Changed
    
    - Moved the internal `apply_patch` argv[1] flag constant out of
    `codex-core` and into `codex-apply-patch`.
    - Renamed the constant to `CODEX_CORE_APPLY_PATCH_ARG1` and documented
    that it is part of the Codex core process-invocation contract (even
    though it now lives in `codex-apply-patch`).
    - Updated `arg0`, the core apply-patch runtime, and the `codex-exec`
    apply-patch test to import the constant from `codex-apply-patch`.
    - Updated `codex-rs/arg0` to call
    `codex_utils_home_dir::find_codex_home()` directly instead of
    `codex_core::config::find_codex_home()`.
    - Removed the `codex-core` dependency from `codex-rs/arg0` and added the
    needed direct dependency on `codex-utils-home-dir`.
    - Added `codex-apply-patch` as a dev-dependency for `codex-rs/exec`
    tests (the apply-patch test now imports the moved constant directly).
    
    ## Verification
    
    - `cargo test -p codex-apply-patch`
    - `cargo test -p codex-arg0`
    - `cargo test -p codex-core --lib apply_patch`
    - `cargo test -p codex-exec
    test_standalone_exec_cli_can_use_apply_patch`
    - `cargo shear`
  • feat: increase windows workers stack (#11736)
    Switched arg0 runtime initialization from tokio::runtime::Runtime::new()
    to an explicit multi-thread builder that sets the thread stack size to
    16MiB.
    
    This is only for Windows for now but we might need to do this for others
    in the future. This is required because Codex becomes quite large and
    Windows tends to consume stack a little bit faster (this is a known
    thing even though everyone seems to have different theory on it)
  • chore(arg0): advisory-lock janitor for codex tmp paths (#10039)
    ## Description
    
    ### What changed
    - Switch the arg0 helper root from `~/.codex/tmp/path` to
    `~/.codex/tmp/path2`
    - Add `Arg0PathEntryGuard` to keep both the `TempDir` and an exclusive
    `.lock` file alive for the process lifetime
    - Add a startup janitor that scans `path2` and deletes only directories
    whose lock can be acquired
    
    ### Tests
    - `cargo clippy -p codex-arg0`
    - `cargo clippy -p codex-core`
    - `cargo test -p codex-arg0`
    - `cargo test -p codex-core`
  • fix: harden arg0 helper PATH handling (#8766)
    ### Motivation
    - Avoid placing PATH entries under the system temp directory by creating
    the helper directory under `CODEX_HOME` instead of
    `std::env::temp_dir()`.
    - Fail fast on unsafe configuration by rejecting `CODEX_HOME` values
    that live under the system temp root to prevent writable PATH entries.
    
    ### Testing
    - Ran `just fmt`, which completed with a non-blocking
    `imports_granularity` warning.
    - Ran `just fix -p codex-arg0` (Clippy fixes) which completed
    successfully.
    - Ran `cargo test -p codex-arg0` and the test run completed
    successfully.
  • feat: add support for building with Bazel (#8875)
    This PR configures Codex CLI so it can be built with
    [Bazel](https://bazel.build) in addition to Cargo. The `.bazelrc`
    includes configuration so that remote builds can be done using
    [BuildBuddy](https://www.buildbuddy.io).
    
    If you are familiar with Bazel, things should work as you expect, e.g.,
    run `bazel test //... --keep-going` to run all the tests in the repo,
    but we have also added some new aliases in the `justfile` for
    convenience:
    
    - `just bazel-test` to run tests locally
    - `just bazel-remote-test` to run tests remotely (currently, the remote
    build is for x86_64 Linux regardless of your host platform). Note we are
    currently seeing the following test failures in the remote build, so we
    still need to figure out what is happening here:
    
    ```
    failures:
        suite::compact::manual_compact_twice_preserves_latest_user_messages
        suite::compact_resume_fork::compact_resume_after_second_compaction_preserves_history
        suite::compact_resume_fork::compact_resume_and_fork_preserve_model_history_view
    ```
    
    - `just build-for-release` to build release binaries for all
    platforms/architectures remotely
    
    To setup remote execution:
    - [Create a buildbuddy account](https://app.buildbuddy.io/) (OpenAI
    employees should also request org access at
    https://openai.buildbuddy.io/join/ with their `@openai.com` email
    address.)
    - [Copy your API key](https://app.buildbuddy.io/docs/setup/) to
    `~/.bazelrc` (add the line `build
    --remote_header=x-buildbuddy-api-key=YOUR_KEY`)
    - Use `--config=remote` in your `bazel` invocations (or add `common
    --config=remote` to your `~/.bazelrc`, or use the `just` commands)
    
    ## CI
    
    In terms of CI, this PR introduces `.github/workflows/bazel.yml`, which
    uses Bazel to run the tests _locally_ on Mac and Linux GitHub runners
    (we are working on supporting Windows, but that is not ready yet). Note
    that the failures we are seeing in `just bazel-remote-test` do not occur
    on these GitHub CI jobs, so everything in `.github/workflows/bazel.yml`
    is green right now.
    
    The `bazel.yml` uses extra config in `.github/workflows/ci.bazelrc` so
    that macOS CI jobs build _remotely_ on Linux hosts (using the
    `docker://docker.io/mbolin491/codex-bazel` Docker image declared in the
    root `BUILD.bazel`) using cross-compilation to build the macOS
    artifacts. Then these artifacts are downloaded locally to GitHub's macOS
    runner so the tests can be executed natively. This is the relevant
    config that enables this:
    
    ```
    common:macos --config=remote
    common:macos --strategy=remote
    common:macos --strategy=TestRunner=darwin-sandbox,local
    ```
    
    Because of the remote caching benefits we get from BuildBuddy, these new
    CI jobs can be extremely fast! For example, consider these two jobs that
    ran all the tests on Linux x86_64:
    
    - Bazel 1m37s
    https://github.com/openai/codex/actions/runs/20861063212/job/59940545209?pr=8875
    - Cargo 9m20s
    https://github.com/openai/codex/actions/runs/20861063192/job/59940559592?pr=8875
    
    For now, we will continue to run both the Bazel and Cargo jobs for PRs,
    but once we add support for Windows and running Clippy, we should be
    able to cutover to using Bazel exclusively for PRs, which should still
    speed things up considerably. We will probably continue to run the Cargo
    jobs post-merge for commits that land on `main` as a sanity check.
    
    Release builds will also continue to be done by Cargo for now.
    
    Earlier attempt at this PR: https://github.com/openai/codex/pull/8832
    Earlier attempt to add support for Buck2, now abandoned:
    https://github.com/openai/codex/pull/8504
    
    ---------
    
    Co-authored-by: David Zbarsky <dzbarsky@gmail.com>
    Co-authored-by: Michael Bolin <mbolin@openai.com>
  • chore: add cargo-deny configuration (#7119)
    - add GitHub workflow running cargo-deny on push/PR
    - document cargo-deny allowlist with workspace-dep notes and advisory
    ignores
    - align workspace crates to inherit version/edition/license for
    consistent checks
  • Use codex-linux-sandbox in unified exec (#6480)
    Unified exec isn't working on Linux because we don't provide the correct
    arg0.
    
    The library we use for pty management doesn't allow setting arg0
    separately from executable. Use the same aliasing strategy we use for
    `apply_patch` for `codex-linux-sandbox`.
    
    Use `#[ctor]` hack to dispatch codex-linux-sandbox calls.
    
    
    Addresses https://github.com/openai/codex/issues/6450
  • chore: clippy on redundant closure (#4058)
    Add redundant closure clippy rules and let Codex fix it by minimising
    FQP
  • chore: unify cargo versions (#4044)
    Unify cargo versions at root
  • [BREAKING] Stop loading project .env files (#3184)
    Loading project local .env often loads settings that break codex cli.
    
    Fixes: https://github.com/openai/codex/issues/3174
  • feat: use the arg0 trick with apply_patch (#2646)
    Historically, Codex CLI has treated `apply_patch` (and its sometimes
    misspelling, `applypatch`) as a "virtual CLI," intercepting it when it
    appears as the first arg to `command` for the `"container.exec",
    `"shell"`, or `"local_shell"` tools.
    
    This approach has a known limitation where if, say, the model created a
    Python script that runs `apply_patch` and then tried to run the Python
    script, we have no insight as to what the model is trying to do and the
    Python Script would fail because `apply_patch` was never really on the
    `PATH`.
    
    One way to solve this problem is to require users to install an
    `apply_patch` executable alongside the `codex` executable (or at least
    put it someplace where Codex can discover it). Though to keep Codex CLI
    as a standalone executable, we exploit "the arg0 trick" where we create
    a temporary directory with an entry named `apply_patch` and prepend that
    directory to the `PATH` for the duration of the invocation of Codex.
    
    - On UNIX, `apply_patch` is a symlink to `codex`, which now changes its
    behavior to behave like `apply_patch` if arg0 is `apply_patch` (or
    `applypatch`)
    - On Windows, `apply_patch.bat` is a batch script that runs `codex
    --codex-run-as-apply-patch %*`, as Codex also changes its behavior if
    the first argument is `--codex-run-as-apply-patch`.
  • chore: upgrade to Rust 1.89 (#2465)
    Codex created this PR from the following prompt:
    
    > upgrade this entire repo to Rust 1.89. Note that this requires
    updating codex-rs/rust-toolchain.toml as well as the workflows in
    .github/. Make sure that things are "clippy clean" as this change will
    likely uncover new Clippy errors. `just fmt` and `cargo clippy --tests`
    are sufficient to check for correctness
    
    Note this modifies a lot of lines because it folds nested `if`
    statements using `&&`.
    
    ---
    [//]: # (BEGIN SAPLING FOOTER)
    Stack created with [Sapling](https://sapling-scm.com). Best reviewed
    with [ReviewStack](https://reviewstack.dev/openai/codex/pull/2465).
    * #2467
    * __->__ #2465
  • fix: do not allow dotenv to create/modify environment variables starting with CODEX_ (#2308)
    This ensures Codex cannot drop a `.env` file with a value of
    `CODEX_HOME` that points to a folder that Codex can control.
  • Auto format toml (#1745)
    Add recommended extension and configure it to auto format prompt.
  • fix: run apply_patch calls through the sandbox (#1705)
    Building on the work of https://github.com/openai/codex/pull/1702, this
    changes how a shell call to `apply_patch` is handled.
    
    Previously, a shell call to `apply_patch` was always handled in-process,
    never leveraging a sandbox. To determine whether the `apply_patch`
    operation could be auto-approved, the
    `is_write_patch_constrained_to_writable_paths()` function would check if
    all the paths listed in the paths were writable. If so, the agent would
    apply the changes listed in the patch.
    
    Unfortunately, this approach afforded a loophole: symlinks!
    
    * For a soft link, we could fix this issue by tracing the link and
    checking whether the target is in the set of writable paths, however...
    * ...For a hard link, things are not as simple. We can run `stat FILE`
    to see if the number of links is greater than 1, but then we would have
    to do something potentially expensive like `find . -inum <inode_number>`
    to find the other paths for `FILE`. Further, even if this worked, this
    approach runs the risk of a
    [TOCTOU](https://en.wikipedia.org/wiki/Time-of-check_to_time-of-use)
    race condition, so it is not robust.
    
    The solution, implemented in this PR, is to take the virtual execution
    of the `apply_patch` CLI into an _actual_ execution using `codex
    --codex-run-as-apply-patch PATCH`, which we can run under the sandbox
    the user specified, just like any other `shell` call.
    
    This, of course, assumes that the sandbox prevents writing through
    symlinks as a mechanism to write to folders that are not in the writable
    set configured by the sandbox. I verified this by testing the following
    on both Mac and Linux:
    
    ```shell
    #!/usr/bin/env bash
    set -euo pipefail
    
    # Can running a command in SANDBOX_DIR write a file in EXPLOIT_DIR?
    
    # Codex is run in SANDBOX_DIR, so writes should be constrianed to this directory.
    SANDBOX_DIR=$(mktemp -d -p "$HOME" sandboxtesttemp.XXXXXX)
    # EXPLOIT_DIR is outside of SANDBOX_DIR, so let's see if we can write to it.
    EXPLOIT_DIR=$(mktemp -d -p "$HOME" sandboxtesttemp.XXXXXX)
    
    echo "SANDBOX_DIR: $SANDBOX_DIR"
    echo "EXPLOIT_DIR: $EXPLOIT_DIR"
    
    cleanup() {
      # Only remove if it looks sane and still exists
      [[ -n "${SANDBOX_DIR:-}" && -d "$SANDBOX_DIR" ]] && rm -rf -- "$SANDBOX_DIR"
      [[ -n "${EXPLOIT_DIR:-}" && -d "$EXPLOIT_DIR" ]] && rm -rf -- "$EXPLOIT_DIR"
    }
    
    trap cleanup EXIT
    
    echo "I am the original content" > "${EXPLOIT_DIR}/original.txt"
    
    # Drop the -s to test hard links.
    ln -s "${EXPLOIT_DIR}/original.txt" "${SANDBOX_DIR}/link-to-original.txt"
    
    cat "${SANDBOX_DIR}/link-to-original.txt"
    
    if [[ "$(uname)" == "Linux" ]]; then
        SANDBOX_SUBCOMMAND=landlock
    else
        SANDBOX_SUBCOMMAND=seatbelt
    fi
    
    # Attempt the exploit
    cd "${SANDBOX_DIR}"
    
    codex debug "${SANDBOX_SUBCOMMAND}" bash -lc "echo pwned > ./link-to-original.txt" || true
    
    cat "${EXPLOIT_DIR}/original.txt"
    ```
    
    Admittedly, this change merits a proper integration test, but I think I
    will have to do that in a follow-up PR.
  • fix: support special --codex-run-as-apply-patch arg (#1702)
    This introduces some special behavior to the CLIs that are using the
    `codex-arg0` crate where if `arg1` is `--codex-run-as-apply-patch`, then
    it will run as if `apply_patch arg2` were invoked. This is important
    because it means we can do things like:
    
    ```
    SANDBOX_TYPE=landlock # or seatbelt for macOS
    codex debug "${SANDBOX_TYPE}" -- codex --codex-run-as-apply-patch PATCH
    ```
    
    which gives us a way to run `apply_patch` while ensuring it adheres to
    the sandbox the user specified.
    
    While it would be nice to use the `arg0` trick like we are currently
    doing for `codex-linux-sandbox`, there is no way to specify the `arg0`
    for the underlying command when running under `/usr/bin/sandbox-exec`,
    so it will not work for us in this case.
    
    Admittedly, we could have also supported this via a custom environment
    variable (e.g., `CODEX_ARG0`), but since environment variables are
    inherited by child processes, that seemed like a potentially leakier
    abstraction.
    
    This change, as well as our existing reliance on checking `arg0`, place
    additional requirements on those who include `codex-core`. Its
    `README.md` has been updated to reflect this.
    
    While we could have just added an `apply-patch` subcommand to the
    `codex` multitool CLI, that would not be sufficient for the standalone
    `codex-exec` CLI, which is something that we distribute as part of our
    GitHub releases for those who know they will not be using the TUI and
    therefore prefer to use a slightly smaller executable:
    
    https://github.com/openai/codex/releases/tag/rust-v0.10.0
    
    To that end, this PR adds an integration test to ensure that the
    `--codex-run-as-apply-patch` option works with the standalone
    `codex-exec` CLI.
    
    ---
    [//]: # (BEGIN SAPLING FOOTER)
    Stack created with [Sapling](https://sapling-scm.com). Best reviewed
    with [ReviewStack](https://reviewstack.dev/openai/codex/pull/1702).
    * #1705
    * #1703
    * __->__ #1702
    * #1698
    * #1697
  • fix: use std::env::args_os instead of std::env::args (#1698)
    Apparently `std::env::args()` will panic during iteration if any
    argument to the process is not valid Unicode:
    
    https://doc.rust-lang.org/std/env/fn.args.html
    
    Let's avoid the risk and just go with `std::env::args_os()`.
    
    ---
    [//]: # (BEGIN SAPLING FOOTER)
    Stack created with [Sapling](https://sapling-scm.com). Best reviewed
    with [ReviewStack](https://reviewstack.dev/openai/codex/pull/1698).
    * #1705
    * #1703
    * #1702
    * __->__ #1698
    * #1697