Compare commits

...

1108 Commits

Author SHA1 Message Date
Ahmed Ibrahim
2dfd05b6c2 remove 2026-01-11 19:48:58 -08:00
Ahmed Ibrahim
3c20ed8900 queue and steer messages 2026-01-11 19:48:19 -08:00
Ahmed Ibrahim
8ce2488dc2 queue and steer messages 2026-01-11 19:45:06 -08:00
Ahmed Ibrahim
1b26719958 Extend OTLP loopback test window 2026-01-11 18:56:11 -08:00
zbarsky-openai
6a57d7980b fix: support remote arm64 builds, as well (#9018) 2026-01-10 18:41:08 -08:00
jif-oai
198289934f Revert "Delete announcement_tip.toml" (#9032)
Reverts openai/codex#9003
2026-01-10 07:30:14 -08:00
charley-oai
6709ad8975 Label attached images so agent can understand in-message labels (#8950)
Agent wouldn't "see" attached images and would instead try to use the
view_file tool:
<img width="1516" height="504" alt="image"
src="https://github.com/user-attachments/assets/68a705bb-f962-4fc1-9087-e932a6859b12"
/>

In this PR, we wrap image content items in XML tags with the name of
each image (now just a numbered name like `[Image #1]`), so that the
model can understand inline image references (based on name). We also
put the image content items above the user message which the model seems
to prefer (maybe it's more used to definitions being before references).

We also tweak the view_file tool description which seemed to help a bit

Results on a simple eval set of images:

Before
<img width="980" height="310" alt="image"
src="https://github.com/user-attachments/assets/ba838651-2565-4684-a12e-81a36641bf86"
/>

After
<img width="918" height="322" alt="image"
src="https://github.com/user-attachments/assets/10a81951-7ee6-415e-a27e-e7a3fd0aee6f"
/>

```json
[
  {
    "id": "single_describe",
    "prompt": "Describe the attached image in one sentence.",
    "images": ["image_a.png"]
  },
  {
    "id": "single_color",
    "prompt": "What is the dominant color in the image? Answer with a single color word.",
    "images": ["image_b.png"]
  },
  {
    "id": "orientation_check",
    "prompt": "Is the image portrait or landscape? Answer in one sentence.",
    "images": ["image_c.png"]
  },
  {
    "id": "detail_request",
    "prompt": "Look closely at the image and call out any small details you notice.",
    "images": ["image_d.png"]
  },
  {
    "id": "two_images_compare",
    "prompt": "I attached two images. Are they the same or different? Briefly explain.",
    "images": ["image_a.png", "image_b.png"]
  },
  {
    "id": "two_images_captions",
    "prompt": "Provide a short caption for each image (Image 1, Image 2).",
    "images": ["image_c.png", "image_d.png"]
  },
  {
    "id": "multi_image_rank",
    "prompt": "Rank the attached images from most colorful to least colorful.",
    "images": ["image_a.png", "image_b.png", "image_c.png"]
  },
  {
    "id": "multi_image_choice",
    "prompt": "Which image looks more vibrant? Answer with 'Image 1' or 'Image 2'.",
    "images": ["image_b.png", "image_d.png"]
  }
]
```
2026-01-09 21:33:45 -08:00
Michael Bolin
cf515142b0 fix: include AGENTS.md as repo root marker for integration tests (#9010)
As explained in `codex-rs/core/BUILD.bazel`, including the repo's own
`AGENTS.md` is a hack to get some tests passing. We should fix this
properly, but I wanted to put stake in the ground ASAP to get `just
bazel-remote-test` working and then add a job to `bazel.yml` to ensure
it keeps working.
2026-01-09 17:09:59 -08:00
Michael Bolin
74b2238931 fix: add .git to .bazelignore (#9008)
As noted in the comment, this was causing a problem for me locally
because Sapling backed up some files under `.git/sl` named `BUILD.bazel`
and so Bazel tried to parse them.

It's a bit surprising that Bazel does not ignore `.git` out of the box
such that you have to opt-in to considering it rather than opting-out.
2026-01-10 00:55:02 +00:00
gt-oai
cc0b5e8504 Add URL to responses error messages (#8984)
Put the URL in error messages, to aid debugging Codex pointing at wrong
endpoints.

<img width="759" height="164" alt="Screenshot 2026-01-09 at 16 32 49"
src="https://github.com/user-attachments/assets/77a0622c-955d-426d-86bb-c035210a4ecc"
/>
2026-01-10 00:53:47 +00:00
gt-oai
8e49a2c0d1 Add model provider info to /status if non-default (#8981)
Add model provider info to /status if non-default

Enterprises are running Codex and migrating between proxied / API key
auth and SIWC. If you accidentally run Codex with `OPENAI_BASE_URL=...`,
which is surprisingly easy to do, we don't tend to surface this anywhere
and it may lead to breakage. One suggestion was to include this
information in `/status`:

<img width="477" height="157" alt="Screenshot 2026-01-09 at 15 45 34"
src="https://github.com/user-attachments/assets/630ce68f-c856-4a2b-a004-7df2fbe5de93"
/>
2026-01-10 00:53:34 +00:00
Ahmed Ibrahim
af1ed2685e Refactor remote models tests to use TestCodex builder (#8940)
- add `with_model_provider` to the test codex builder
- replace the bespoke remote models harness with `TestCodex` in
`remote_models` tests
2026-01-09 15:11:56 -08:00
pakrym-oai
1a0e2e612b Delete announcement_tip.toml (#9003) 2026-01-09 14:47:46 -08:00
pakrym-oai
acfd94f625 Add hierarchical agent prompt (#8996) 2026-01-09 13:47:37 -08:00
pakrym-oai
cabf85aa18 Log unhandled sse events (#8949) 2026-01-09 12:36:07 -08:00
viyatb-oai
bc284669c2 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.
2026-01-09 12:35:54 -08:00
Owen Lin
fbe883318d fix(app-server): set originator header from initialize (re-revert) (#8988)
Reapplies https://github.com/openai/codex/pull/8873 which was reverted
due to merge conflicts
2026-01-09 12:09:30 -08:00
zbarsky-openai
2a06d64bc9 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>
2026-01-09 11:09:43 -08:00
Helmut Januschka
7daaabc795 fix: add tui.alternate_screen config and --no-alt-screen CLI flag for Zellij scrollback (#8555)
Fixes #2558

Codex uses alternate screen mode (CSI 1049) which, per xterm spec,
doesn't support scrollback. Zellij follows this strictly, so users can't
scroll back through output.

**Changes:**
- Add `tui.alternate_screen` config: `auto` (default), `always`, `never`
- Add `--no-alt-screen` CLI flag
- Auto-detect Zellij and skip alt screen (uses existing `ZELLIJ` env var
detection)

**Usage:**
```bash
# CLI flag
codex --no-alt-screen

# Or in config.toml
[tui]
alternate_screen = "never"
```

With default `auto` mode, Zellij users get working scrollback without
any config changes.

---------

Co-authored-by: Josh McKinney <joshka@openai.com>
2026-01-09 18:38:26 +00:00
jif-oai
1aed01e99f renaming: task to turn (#8963) 2026-01-09 17:31:17 +00:00
jif-oai
ed64804cb5 nit: rename to analytics_enabled (#8978) 2026-01-09 17:18:42 +00:00
jif-oai
5c380d5b1e Revert "fix(app-server): set originator header from initialize JSON-RPC request" (#8986)
Reverts openai/codex#8873
2026-01-09 17:00:53 +00:00
jif-oai
46b0c4acbb chore: nuke telemetry file (#8985) 2026-01-09 08:55:21 -08:00
gt-oai
5b5a5b92b5 Add config to disable /feedback (#8909)
Some enterprises do not want their users to be able to `/feedback`.

<img width="395" height="325" alt="image"
src="https://github.com/user-attachments/assets/2dae9c0b-20c3-4a15-bcd3-0187857ebbd8"
/>

Adds to `config.toml`:

```toml
[feedback]
enabled = false
```

I've deliberately decided to:
1. leave other references to `/feedback` (e.g. in the interrupt message,
tips of the day) unchanged. I think we should continue to promote the
feature even if it is not usable currently.
2. leave the `/feedback` menu item selectable and display an error
saying it's disabled, rather than remove the menu item (which I believe
would raise more questions).

but happy to discuss these.

This will be followed by a change to requirements.toml that admins can
use to force the value of feedback.enabled.
2026-01-09 16:33:48 +00:00
Owen Lin
ea56186c2b fix(app-server): set originator header from initialize JSON-RPC request (#8873)
**Motivation**
The `originator` header is important for codex-backend’s Responses API
proxy because it identifies the real end client (codex cli, codex vscode
extension, codex exec, future IDEs) and is used to categorize requests
by client for our enterprise compliance API.

Today the `originator` header is set by either:
- the `CODEX_INTERNAL_ORIGINATOR_OVERRIDE` env var (our VSCode extension
does this)
- calling `set_default_originator()` which sets a global immutable
singleton (`codex exec` does this)

For `codex app-server`, we want the `initialize` JSON-RPC request to set
that header because it is a natural place to do so. Example:
```json
{
  "method": "initialize",
  "id": 0,
  "params": {
    "clientInfo": {
      "name": "codex_vscode",
      "title": "Codex VS Code Extension",
      "version": "0.1.0"
    }
  }
}
```
and when app-server receives that request, it can call
`set_default_originator()`. This is a much more natural interface than
asking third party developers to set an env var.

One hiccup is that `originator()` reads the global singleton and locks
in the value, preventing a later `set_default_originator()` call from
setting it. This would be fine but is brittle, since any codepath that
calls `originator()` before app-server can process an `initialize`
JSON-RPC call would prevent app-server from setting it. This was
actually the case with OTEL initialization which runs on boot, but I
also saw this behavior in certain tests.

Instead, what we now do is:
- [unchanged] If `CODEX_INTERNAL_ORIGINATOR_OVERRIDE` env var is set,
`originator()` would return that value and `set_default_originator()`
with some other value does NOT override it.
- [new] If no env var is set, `originator()` would return the default
value which is `codex_cli_rs` UNTIL `set_default_originator()` is called
once, in which case it is set to the new value and becomes immutable.
Later calls to `set_default_originator()` returns
`SetOriginatorError::AlreadyInitialized`.

**Other notes**
- I updated `codex_core::otel_init::build_provider` to accepts a service
name override, and app-server sends a hardcoded `codex_app_server`
service name to distinguish it from `codex_cli_rs` used by default (e.g.
TUI).

**Next steps**
- Update VSCE to set the proper value for `clientInfo.name` on
`initialize` and drop the `CODEX_INTERNAL_ORIGINATOR_OVERRIDE` env var.
- Delete support for `CODEX_INTERNAL_ORIGINATOR_OVERRIDE` in codex-rs.
2026-01-09 08:17:13 -08:00
Eric Traut
cacdae8c05 Work around crash in system-configuration library (#8954)
This is a proposed fix for #8912

Information provided by Codex:

no_proxy means “don’t use any system proxy settings for this client,”
even if macOS has proxies configured in System Settings or via
environment. On macOS, reqwest’s proxy discovery can call into the
system-configuration framework; that’s the code path that was panicking
with “Attempted to create a NULL object.” By forcing a direct connection
for the OAuth discovery request, we avoid that proxy-resolution path
entirely, so the system-configuration crate never gets invoked and the
panic disappears.

Effectively:

With proxies: reqwest asks the OS for proxy config →
system-configuration gets touched → panic.
With no_proxy: reqwest skips proxy lookup → no system-configuration call
→ no panic.
So the fix doesn’t change any MCP protocol behavior; it just prevents
the OAuth discovery probe from touching the macOS proxy APIs that are
crashing in the reported environment.

This fix changes behavior for the OAuth discovery probe used in codex
mcp list/auth status detection. With no_proxy, that probe won’t use
system or env proxy settings, so:

If a server is only reachable via a proxy, the discovery call may fail
and we’ll show auth as Unsupported/NotLoggedIn incorrectly.
If the server is reachable directly (common case), behavior is
unchanged.



As an alternative, we could try to get a fix into the
[system-configuration](https://github.com/mullvad/system-configuration-rs)
library. It looks like this library is still under development but has
slow release pace.
2026-01-09 08:11:34 -07:00
jif-oai
bc92dc5cf0 chore: update metrics temporality (#8901) 2026-01-09 14:57:42 +00:00
jif-oai
7e5b3e069e chore: metrics tool call (#8975) 2026-01-09 13:28:43 +00:00
jif-oai
e2e3f4490e chore: add approval metric (#8970) 2026-01-09 13:10:31 +00:00
jif-oai
225614d7fb chore: add mcp call metric (#8973) 2026-01-09 13:09:21 +00:00
jif-oai
16c66c37eb chore: move otel provider outside of trace module (#8968) 2026-01-09 12:42:54 +00:00
jif-oai
e9c548c65e chore: non mutable btree when building specs (#8969) 2026-01-09 12:21:55 +00:00
jif-oai
fceae86581 nit: rename session metric (#8966) 2026-01-09 11:55:57 +00:00
jif-oai
568b938c80 feat: first pass on clb tool (#8930) 2026-01-09 11:54:05 +00:00
Matthew Zeng
24d6e0114f [device-auth] When headless environment is detected, show device login flow instead. (#8756)
When headless environment is detected, show device login flow instead.
2026-01-08 21:48:30 -08:00
Michael Bolin
d3ff668f68 fix: remove existing process hardening from Codex CLI (#8951)
As explained in https://github.com/openai/codex/issues/8945 and
https://github.com/openai/codex/issues/8472, there are legitimate cases
where users expect processes spawned by Codex to inherit environment
variables such as `LD_LIBRARY_PATH` and `DYLD_LIBRARY_PATH`, where
failing to do so can cause significant performance issues.

This PR removes the use of
`codex_process_hardening::pre_main_hardening()` in Codex CLI (which was
added not in response to a known security issue, but because it seemed
like a prudent thing to do from a security perspective:
https://github.com/openai/codex/pull/4521), but we will continue to use
it in `codex-responses-api-proxy`. At some point, we probably want to
introduce a slightly different version of
`codex_process_hardening::pre_main_hardening()` in Codex CLI that
excludes said environment variables from the Codex process itself, but
continues to propagate them to subprocesses.
2026-01-08 21:19:34 -08:00
Ahmed Ibrahim
81caee3400 Add 5s timeout to models list call + integration test (#8942)
- Enforce a 5s timeout around the remote models refresh to avoid hanging
/models calls.
2026-01-08 18:06:10 -08:00
Thibault Sottiaux
51dd5af807 fix: treat null MCP resource args as empty (#8917)
Handle null tool arguments in the MCP resource handler so optional
resource tools accept null without failing, preserving normal JSON
parsing for non-null payloads and improving robustness when models emit
null; this avoids spurious argument parse errors for list/read MCP
resource calls.
2026-01-08 17:47:02 -08:00
iceweasel-oai
6372ba9d5f Elevated sandbox NUX (#8789)
Elevated Sandbox NUX:

* prompt for elevated sandbox setup when agent mode is selected (via
/approvals or at startup)
* prompt for degraded sandbox if elevated setup is declined or fails
* introduce /elevate-sandbox command to upgrade from degraded
experience.
2026-01-08 16:23:06 -08:00
Michael Bolin
bdfdebcfa1 fix: increase timeout for wait_for_event() for Bazel (#8946)
This seems to be necessary to get the Bazel builds on ARM Linux to go
green on https://github.com/openai/codex/pull/8875.

I don't feel great about timeout-whack-a-mole, but we're still learning
here...
2026-01-08 15:37:46 -08:00
pakrym-oai
62a73b6d58 Attempt to reload auth as a step in 401 recovery (#8880)
When authentication fails, first attempt to reload the auth from file
and then attempt to refresh it.
2026-01-08 15:06:44 -08:00
Celia Chen
be4364bb80 [chore] move app server tests from chat completion to responses (#8939)
We are deprecating chat completions. Move all app server tests from chat
completion to responses.
2026-01-08 22:27:55 +00:00
Ahmed Ibrahim
0d3e673019 remove get_responses_requests and get_responses_request_bodies to use in-place matcher (#8858) 2026-01-08 13:57:48 -08:00
Anton Panasenko
41a317321d feat: fork conversation/thread (#8866)
## Summary
- add thread/conversation fork endpoints to the protocol (v1 + v2)
- implement fork handling in app-server using thread manager and config
overrides
- add fork coverage in app-server tests and document `thread/fork` usage
2026-01-08 12:54:20 -08:00
Celia Chen
051bf81df9 [fix] app server flaky send_messages test (#8874)
Fix flakiness of CI test:
https://github.com/openai/codex/actions/runs/20350530276/job/58473691434?pr=8282

This PR does two things:
1. move the flakiness test to use responses API instead of chat
completion API
2. make mcp_process agnostic to the order of
responses/notifications/requests that come in, by buffering messages not
read
2026-01-08 20:41:21 +00:00
Michael Bolin
a70f5b0b3c fix: correct login shell mismatch in the accept_elicitation_for_prompt_rule() test (#8931)
Because the path to `git` is used to construct `elicitations_to_accept`,
we need to ensure that we resolve which `git` to use the same way our
Bash process will:


c9c6560685/codex-rs/exec-server/tests/suite/accept_elicitation.rs (L59-L69)

This fixes an issue when running the test on macOS using Bazel
(https://github.com/openai/codex/pull/8875) where the login shell chose
`/opt/homebrew/bin/git` whereas the non-login shell chose
`/usr/bin/git`.
2026-01-08 12:37:38 -08:00
Michael Bolin
224c4867dd fix: increase timeout for tests that have been flaking with timeout issues (#8932)
I have seen this test flake out sometimes when running the macOS build
using Bazel in CI: https://github.com/openai/codex/pull/8875. Perhaps
Bazel runs with greater parallelism, inducing a heavier load, causing an
issue?
2026-01-08 20:31:03 +00:00
jif-oai
c9c6560685 nit: parse_arguments (#8927) 2026-01-08 19:49:17 +00:00
pakrym-oai
634764ece9 Immutable CodexAuth (#8857)
Historically we started with a CodexAuth that knew how to refresh it's
own tokens and then added AuthManager that did a different kind of
refresh (re-reading from disk).

I don't think it makes sense for both `CodexAuth` and `AuthManager` to
be mutable and contain behaviors.

Move all refresh logic into `AuthManager` and keep `CodexAuth` as a data
object.
2026-01-08 11:43:56 -08:00
Felipe Petroski Such
5bc3e325a6 add tooltip hint for shell commands (!) (#8926)
I didn't know this existed because its not listed in the hints.
2026-01-08 19:31:20 +00:00
gt-oai
4156060416 Add read-only when backfilling requirements from managed_config (#8913)
When a user has a managed_config which doesn't specify read-only, Codex
fails to launch.
2026-01-08 11:27:46 -08:00
Thibault Sottiaux
98122cbad0 fix: preserve core env vars on Windows (#8897)
This updates core shell environment policy handling to match Windows
case-insensitive variable names and adds a Windows-only regression test,
so Path/TEMP are no longer dropped when inherit=core.
2026-01-08 10:36:36 -08:00
github-actions[bot]
7b21b443bb Update models.json (#8792)
Automated update of models.json.

Co-authored-by: aibrahim-oai <219906144+aibrahim-oai@users.noreply.github.com>
2026-01-08 10:26:01 -08:00
gt-oai
93dec9045e otel test: retry WouldBlock errors (#8915)
This test looks flaky on Windows:

```
        FAIL [   0.034s] (1442/2802) codex-otel::tests suite::otlp_http_loopback::otlp_http_exporter_sends_metrics_to_collector
  stdout ───

    running 1 test
    test suite::otlp_http_loopback::otlp_http_exporter_sends_metrics_to_collector ... FAILED

    failures:

    failures:
        suite::otlp_http_loopback::otlp_http_exporter_sends_metrics_to_collector

    test result: FAILED. 0 passed; 1 failed; 0 ignored; 0 measured; 14 filtered out; finished in 0.02s
    
  stderr ───
    Error: ProviderShutdown { source: InternalFailure("[InternalFailure(\"Failed to shutdown\")]") }

────────────
     Summary [ 175.360s] 2802 tests run: 2801 passed, 1 failed, 15 skipped
        FAIL [   0.034s] (1442/2802) codex-otel::tests suite::otlp_http_loopback::otlp_http_exporter_sends_metrics_to_collector
```
2026-01-08 18:18:49 +00:00
jif-oai
69898e3dba clean: all history cloning (#8916) 2026-01-08 18:17:18 +00:00
Celia Chen
c4af304c77 [fix] app server flaky thread/resume tests (#8870)
Fix flakiness of CI tests:
https://github.com/openai/codex/actions/runs/20350530276/job/58473691443?pr=8282

This PR does two things:
1. test with responses API instead of chat completions API in
thread_resume tests;
2. have a new responses API fixture that mocks out arbitrary numbers of
responses API calls (including no calls) and have the same repeated
response.

Tested by CI
2026-01-08 10:17:05 -08:00
jif-oai
5b7707dfb1 feat: add list loaded threads to app server (#8902) 2026-01-08 17:48:20 +00:00
Michael Bolin
59d6937550 fix: reduce duplicate include_str!() calls (#8914) 2026-01-08 17:20:41 +00:00
gt-oai
932a5a446f config requirements: improve requirement error messages (#8843)
**Before:**
```
Error loading configuration: value `Never` is not in the allowed set [OnRequest]
```

**After:**
```
Error loading configuration: invalid value for `approval_policy`: `Never` is not in the
allowed set [OnRequest] (set by MDM com.openai.codex:requirements_toml_base64)
```

Done by introducing a new struct `ConfigRequirementsWithSources` onto
which we `merge_unset_fields` now. Also introduces a pair of requirement
value and its `RequirementSource` (inspired by `ConfigLayerSource`):

```rust
pub struct Sourced<T> {
    pub value: T,
    pub source: RequirementSource,
}
```
2026-01-08 16:11:14 +00:00
zbarsky-openai
484f6f4c26 gitignore bazel-* (#8911)
QoL improvement so we don't accidentally add these dirs while we
prototype bazel things
2026-01-08 07:50:58 -08:00
jif-oai
5522663f92 feat: add a few metrics (#8910) 2026-01-08 15:39:57 +00:00
jif-oai
98e171258c nit: drop unused function call error (#8903) 2026-01-08 15:07:30 +00:00
jif-oai
da667b1f56 chore: drop useless interaction_input (#8907) 2026-01-08 15:01:07 +00:00
Michael Bolin
1e29774fce fix: leverage codex_utils_cargo_bin() in codex-rs/core/tests/suite (#8887)
This eliminates our dependency on the `escargot` crate and better
prepares us for Bazel builds: https://github.com/openai/codex/pull/8875.
2026-01-08 14:56:16 +00:00
Denis Andrejew
9ce6bbc43e Avoid setpgid for inherited stdio on macOS (#8691)
## Summary
- avoid setting a new process group when stdio is inherited (keeps child
in foreground PG)
- keep process-group isolation when stdio is redirected so killpg
cleanup still works
- prevents macOS job-control SIGTTIN stops that look like hangs after
output

## Testing
- `cargo build -p codex-cli`
- `GIT_CONFIG_GLOBAL=/dev/null GIT_CONFIG_NOSYSTEM=1
CARGO_BIN_EXE_codex=/Users/denis/Code/codex/codex-rs/target/debug/codex
/opt/homebrew/bin/timeout 30m cargo test -p codex-core -p codex-exec`

## Context
This fixes macOS sandbox hangs for commands like `elixir -v` / `erl
-noshell`, where the child was moved into a new process group while
still attached to the controlling TTY. See issue #8690.

## Authorship & collaboration
- This change and analysis were authored by **Codex** (AI coding agent).
- Human collaborator: @seeekr provided repro environment, context, and
review guidance.
- CLI used: `codex-cli 0.77.0`.
- Model: `gpt-5.2-codex (xhigh)`.

Co-authored-by: Eric Traut <etraut@openai.com>
2026-01-08 07:50:40 -07:00
Michael Bolin
7520d8ba58 fix: leverage find_resource! macro in load_sse_fixture_with_id (#8888)
This helps prepare us for Bazel builds:
https://github.com/openai/codex/pull/8875.
2026-01-08 09:34:05 -05:00
jif-oai
0318f30ed8 chore: add small debug client (#8894)
Small debug client, do not use in production
2026-01-08 13:40:14 +00:00
Thibault Sottiaux
be212db0c8 fix: include project instructions in /review subagent (#8899)
Include project-level AGENTS.md and skills in /review sessions so the
review sub-agent uses the same instruction pipeline as standard runs,
keeping reviewer context aligned with normal sessions.
2026-01-08 13:31:01 +00:00
Thibault Sottiaux
5b022c2904 chore: align error limit comment (#8896) 2026-01-08 13:30:33 +00:00
jif-oai
e21ce6c5de chore: drop metrics exporter config (#8892)
Dropped for now as enterprises should not be able to use it
2026-01-08 13:20:18 +00:00
Thibault Sottiaux
267c05fb30 fix: stabilize list_dir pagination order (#8826)
Sort list_dir entries before applying offset/limit so pagination matches
the displayed order, update pagination/truncation expectations, and add
coverage for sorted pagination. This ensures stable, predictable
directory pages when list_dir is enabled.
2026-01-08 03:51:47 -08:00
jif-oai
634650dd25 feat: metrics capabilities (#8318)
Add metrics capabilities to Codex. The `README.md` is up to date.

This will not be merged with the metrics before this PR of course:
https://github.com/openai/codex/pull/8350
2026-01-08 11:47:36 +00:00
jif-oai
8a0c2e5841 chore: add list thread ids on manager (#8855) 2026-01-08 10:53:58 +00:00
Dylan Hurd
0f8bb4579b fix: windows can now paste non-ascii multiline text (#8774)
## Summary
This PR builds _heavily_ on the work from @occurrent in #8021 - I've
only added a small fix, added additional tests, and propagated the
changes to tui2.

From the original PR:

> On Windows, Codex relies on PasteBurst for paste detection because
bracketed paste is not reliably available via crossterm.
> 
> When pasted content starts with non-ASCII characters, input is routed
through handle_non_ascii_char, which bypasses the normal paste burst
logic. This change extends the paste burst window for that path, which
should ensure that Enter is correctly grouped as part of the paste.


## Testing
- [x] tested locally cross-platform
- [x] added regression tests

---------

Co-authored-by: occur <occurring@outlook.com>
2026-01-07 23:21:49 -08:00
Michael Bolin
35fd69a9f0 fix: make the find_resource! macro responsible for the absolutize() call (#8884)
https://github.com/openai/codex/pull/8879 introduced the
`find_resource!` macro, but now that I am about to use it in more
places, I realize that it should take care of this normalization case
for callers.

Note the `use $crate::path_absolutize::Absolutize;` line is there so
that users of `find_resource!` do not have to explicitly include
`path-absolutize` to their own `Cargo.toml`.
2026-01-07 23:03:43 -08:00
iceweasel-oai
ccba737d26 add ability to disable input temporarily in the TUI. (#8876)
We will disable input while the elevated sandbox setup is running.
2026-01-07 20:56:48 -08:00
xl-openai
75076aabfe Support UserInput::Skill in V2 API. (#8864)
Allow client to specify explicit skill invocation in v2 API.
2026-01-07 18:26:35 -08:00
Michael Bolin
f6b563ec64 feat: introduce find_resource! macro that works with Cargo or Bazel (#8879)
To support Bazelification in https://github.com/openai/codex/pull/8875,
this PR introduces a new `find_resource!` macro that we use in place of
our existing logic in tests that looks for resources relative to the
compile-time `CARGO_MANIFEST_DIR` env var.

To make this work, we plan to add the following to all `rust_library()`
and `rust_test()` Bazel rules in the project:

```
rustc_env = {
    "BAZEL_PACKAGE": native.package_name(),
},
```

Our new `find_resource!` macro reads this value via
`option_env!("BAZEL_PACKAGE")` so that the Bazel package _of the code
using `find_resource!`_ is injected into the code expanded from the
macro. (If `find_resource()` were a function, then
`option_env!("BAZEL_PACKAGE")` would always be
`codex-rs/utils/cargo-bin`, which is not what we want.)

Note we only consider the `BAZEL_PACKAGE` value when the `RUNFILES_DIR`
environment variable is set at runtime, indicating that the test is
being run by Bazel. In this case, we have to concatenate the runtime
`RUNFILES_DIR` with the compile-time `BAZEL_PACKAGE` value to build the
path to the resource.

In testing this change, I discovered one funky edge case in
`codex-rs/exec-server/tests/common/lib.rs` where we have to _normalize_
(but not canonicalize!) the result from `find_resource!` because the
path contains a `common/..` component that does not exist on disk when
the test is run under Bazel, so it must be semantically normalized using
the [`path-absolutize`](https://crates.io/crates/path-absolutize) crate
before it is passed to `dotslash fetch`.

Because this new behavior may be non-obvious, this PR also updates
`AGENTS.md` to make humans/Codex aware that this API is preferred.
2026-01-07 18:06:08 -08:00
iceweasel-oai
357e4c902b add footer note to TUI (#8867)
This will be used by the elevated sandbox NUX to give a hint on how to
run the elevated sandbox when in the non-elevated mode.
2026-01-07 16:44:28 -08:00
Michael Bolin
ef8b8ebc94 fix: use tokio for I/O in an async function (#8868)
I thought this might solve a bug I'm working on, but it turned out to be
a red herring. Nevertheless, this seems like the right thing to do here.
2026-01-07 16:36:23 -08:00
Michael Bolin
54b290ec1d fix: update resource path resolution logic so it works with Bazel (#8861)
The Bazelification work in-flight over at
https://github.com/openai/codex/pull/8832 needs this fix so that Bazel
can find the path to the DotSlash file for `bash`.

With this change, the following almost works:

```
bazel test --test_output=errors //codex-rs/exec-server:exec-server-all-test
```

That is, now the `list_tools` test passes, but
`accept_elicitation_for_prompt_rule` still fails because it runs
Seatbelt itself, so it needs to be run outside Bazel's local sandboxing.
2026-01-07 22:33:05 +00:00
Shijie Rao
efd0c21b9b Feat: appServer.requirementList for requirement.toml (#8800)
### Summary
We are exposing requirements via `requirement/list` method from
app-server so that we can conditionally disable the agent mode dropdown
selection in VSCE and correctly setting the default value.

### Sample output
#### `etc/codex/requirements.toml`
<img width="497" height="49" alt="Screenshot 2026-01-06 at 11 32 06 PM"
src="https://github.com/user-attachments/assets/fbd9402e-515f-4b9e-a158-2abb23e866a0"
/>

#### App server response
<img width="1107" height="79" alt="Screenshot 2026-01-06 at 11 30 18 PM"
src="https://github.com/user-attachments/assets/c0d669cd-54ef-4789-a26c-adb2c41950af"
/>
2026-01-07 13:57:44 -08:00
xl-openai
61e81af887 Support symlink for skills discovery. (#8801)
Skills discovery now follows symlink entries for SkillScope::User
($CODEX_HOME/skills) and SkillScope::Admin (e.g. /etc/codex/skills).

Added cycle protection: directories are canonicalized and tracked in a
visited set to prevent infinite traversal from circular links.

Added per-root traversal limits to avoid accidentally scanning huge
trees:
- max depth: 6
- max directories: 2000 (logs a warning if truncated)

For now, symlink stat failures and traversal truncation are logged
rather than surfaced as UI “invalid SKILL.md” warnings.
2026-01-07 13:34:48 -08:00
gt-oai
f07b8aa591 Warn in /model if BASE_URL set (#8847)
<img width="763" height="349" alt="Screenshot 2026-01-07 at 18 37 59"
src="https://github.com/user-attachments/assets/569d01cb-ea91-4113-889b-ba74df24adaf"
/>

It may not make sense to use the `/model` menu with a custom
OPENAI_BASE_URL. But some model proxies may support it, so we shouldn't
disable it completely. A warning is a reasonable compromise.
2026-01-07 21:24:18 +00:00
darlingm
5f3f70203c Clarify YAML frontmatter formatting in skill-creator (#8610)
Fixes #8609

# Summary

Emphasize single-line name/description values and quoting when values
could be interpreted as YAML syntax.

# Testing

Not run (skill-only change.)
2026-01-07 14:24:02 -07:00
Channing Conger
21c6d40a44 Add feature for optional request compression (#8767)
Adds a new feature
`enable_request_compression` that will compress using zstd requests to
the codex-backend. Currently only enabled for codex-backend so only enabled for openai providers when using chatgpt::auth even when the feature is enabled

Added a new info log line too for evaluating the compression ratio and
overhead off compressing before requesting. You can enable with
`RUST_LOG=$RUST_LOG,codex_client::transport=info`

```
2026-01-06T00:09:48.272113Z  INFO codex_client::transport: Compressed request body with zstd pre_compression_bytes=28914 post_compression_bytes=11485 compression_duration_ms=0
```
2026-01-07 13:21:40 -08:00
Ahmed Ibrahim
a9b5e8a136 Simplify error managment in run_turn (#8849) 2026-01-07 13:15:46 -08:00
Ahmed Ibrahim
187924d761 Override truncation policy at model info level (#8856)
We used to override truncation policy by comparing model info vs config
value in context manager. A better way to do it is to construct model
info using the config value
2026-01-07 13:06:20 -08:00
Owen Lin
66450f0445 fix: implement 'Allow this session' for apply_patch approvals (#8451)
**Summary**
This PR makes “ApprovalDecision::AcceptForSession / don’t ask again this
session” actually work for `apply_patch` approvals by caching approvals
based on absolute file paths in codex-core, properly wiring it through
app-server v2, and exposing the choice in both TUI and TUI2.
- This brings `apply_patch` calls to be at feature-parity with general
shell commands, which also have a "Yes, and don't ask again" option.
- This also fixes VSCE's "Allow this session" button to actually work.

While we're at it, also split the app-server v2 protocol's
`ApprovalDecision` enum so execpolicy amendments are only available for
command execution approvals.

**Key changes**
- Core: per-session patch approval allowlist keyed by absolute file
paths
- Handles multi-file patches and renames/moves by recording both source
and destination paths for `Update { move_path: Some(...) }`.
- Extend the `Approvable` trait and `ApplyPatchRuntime` to work with
multiple keys, because an `apply_patch` tool call can modify multiple
files. For a request to be auto-approved, we will need to check that all
file paths have been approved previously.
- App-server v2: honor AcceptForSession for file changes
- File-change approval responses now map AcceptForSession to
ReviewDecision::ApprovedForSession (no longer downgraded to plain
Approved).
- Replace `ApprovalDecision` with two enums:
`CommandExecutionApprovalDecision` and `FileChangeApprovalDecision`
- TUI / TUI2: expose “don’t ask again for these files this session”
- Patch approval overlays now include a third option (“Yes, and don’t
ask again for these files this session (s)”).
    - Snapshot updates for the approval modal.

**Tests added/updated**
- Core:
- Integration test that proves ApprovedForSession on a patch skips the
next patch prompt for the same file
- App-server:
- v2 integration test verifying
FileChangeApprovalDecision::AcceptForSession works properly

**User-visible behavior**
- When the user approves a patch “for session”, future patches touching
only those previously approved file(s) will no longer prompt gain during
that session (both via app-server v2 and TUI/TUI2).

**Manual testing**
Tested both TUI and TUI2 - see screenshots below.

TUI:
<img width="1082" height="355" alt="image"
src="https://github.com/user-attachments/assets/adcf45ad-d428-498d-92fc-1a0a420878d9"
/>


TUI2:
<img width="1089" height="438" alt="image"
src="https://github.com/user-attachments/assets/dd768b1a-2f5f-4bd6-98fd-e52c1d3abd9e"
/>
2026-01-07 20:11:12 +00:00
Celia Chen
e8421c761c [chore] update app server doc with skills (#8853) 2026-01-07 20:07:01 +00:00
jif-oai
fe460e0f9a chore: drop some deprecated (#8848) 2026-01-07 19:54:45 +00:00
jif-oai
1253d19641 chore: drop useless feature flags (#8850) 2026-01-07 19:54:32 +00:00
Ahmed Ibrahim
4c9b4b684f Fix app-server write_models_cache to treat models with less priority number as higher priority. (#8844)
Rank models with p0 higher than p1. This shouldn't result in any
behavioral changes. Just reordering.
2026-01-07 11:22:13 -08:00
pakrym-oai
018de994b0 Stop using AuthManager as the source of codex_home (#8846) 2026-01-07 18:56:20 +00:00
Ahmed Ibrahim
c31960b13a remove unnecessary todos (#8842)
> // todo(aibrahim): why are we passing model here while it can change?

we update it on each turn with `.with_model`

> //TODO(aibrahim): run CI in release mode.

although it's good to have, release builds take double the time tests
take.

> // todo(aibrahim): make this async function

we figured out another way of doing this sync
2026-01-07 10:43:10 -08:00
Ahmed Ibrahim
9179c9deac Merge Modelfamily into modelinfo (#8763)
- Merge ModelFamily into ModelInfo
- Remove logic for adding instructions to apply patch
- Add compaction limit and visible context window to `ModelInfo`
2026-01-07 10:35:09 -08:00
Michael Bolin
a1e81180f8 fix: upgrade lru crate to 0.16.3 (#8845)
See https://rustsec.org/advisories/RUSTSEC-2026-0002.

Though our `ratatui` fork has a transitive dep on an older version of
the `lru` crate, so to get CI green ASAP, this PR also adds an exception
to `deny.toml` for `RUSTSEC-2026-0002`, but hopefully this will be
short-lived.
2026-01-07 10:11:27 -08:00
pakrym-oai
fedcb8f63c Move tests below auth manager (#8840)
To simplify future diffs
2026-01-07 17:36:44 +00:00
jif-oai
116059c3a0 chore: unify conversation with thread name (#8830)
Done and verified by Codex + refactor feature of RustRover
2026-01-07 17:04:53 +00:00
Thibault Sottiaux
0d788e6263 fix: handle early codex exec exit (#8825)
Fixes CodexExec to avoid missing early process exits by registering the
exit handler up front and deferring the error until after stdout is
drained, and adds a regression test that simulates a fast-exit child
while still producing output so hangs are caught.
2026-01-07 08:54:27 -08:00
jif-oai
4cef89a122 chore: rename unified exec sessions (#8822)
Renaming done by Codex
2026-01-07 16:12:47 +00:00
Thibault Sottiaux
124a09e577 fix: handle /review arguments in TUI (#8823)
Handle /review <instructions> in the TUI and TUI2 by routing it as a
custom review command instead of plain text, wiring command dispatch and
adding composer coverage so typing /review text starts a review directly
rather than posting a message. User impact: /review with arguments now
kicks off the review flow, previously it would just forward as a plain
command and not actually start a review.
2026-01-07 13:14:55 +00:00
Thibault Sottiaux
a59052341d fix: parse git apply paths correctly (#8824)
Fixes apply.rs path parsing so 
- quoted diff headers are tokenized and extracted correctly, 
- /dev/null headers are ignored before prefix stripping to avoid bogus
dev/null paths, and
- git apply output paths are unescaped from C-style quoting.

**Why**
This prevents potentially missed staging and misclassified paths when
applying or reverting patches, which could lead to incorrect behavior
for repos with spaces or escaped characters in filenames.

**Impact**
I checked and this is only used in the cloud tasks support and `codex
apply <task_id>` flow.
2026-01-07 13:00:31 +00:00
jif-oai
8372d61be7 chore: silent just fmt (#8820)
Done to avoid spammy warnings to end up in the model context without
having to switch to nightly
```
Warning: can't set `imports_granularity = Item`, unstable features are only available in nightly channel.
```
2026-01-07 12:16:38 +00:00
Thibault Sottiaux
230a045ac9 chore: stabilize core tool parallelism test (#8805)
Set login=false for the shell tool in the timing-based parallelism test
so it does not depend on slow user login shells, making the test
deterministic without user-facing changes. This prevents occasional
flakes when running locally.
2026-01-07 09:26:47 +00:00
charley-oai
3389465c8d Enable model upgrade popup even when selected model is no longer in picker (#8802)
With `config.toml`:
```
model = "gpt-5.1-codex"
```
(where `gpt-5.1-codex` has `show_in_picker: false` in
[`model_presets.rs`](https://github.com/openai/codex/blob/main/codex-rs/core/src/models_manager/model_presets.rs);
this happens if the user hasn't used codex in a while so they didn't see
the popup before their model was changed to `show_in_picker: false`)

The upgrade picker used to not show (because `gpt-5.1-codex` was
filtered out of the model list in code). Now, the filtering is done
downstream in tui and app-server, so the model upgrade popup shows:

<img width="1503" height="227" alt="Screenshot 2026-01-06 at 5 04 37 PM"
src="https://github.com/user-attachments/assets/26144cc2-0b3f-4674-ac17-e476781ec548"
/>
2026-01-06 19:32:27 -08:00
Thibault Sottiaux
8b4d27dfcd fix: truncate long approval prefixes when rendering (#8734)
Fixes inscrutable multiline approval requests:
<img width="686" height="844" alt="image"
src="https://github.com/user-attachments/assets/cf9493dc-79e6-4168-8020-0ef0fe676d5e"
/>
2026-01-06 15:17:01 -08:00
Michael Bolin
dc1a568dc7 fix: populate the release notes when the release is created (#8799)
Use the contents of the commit message from the commit associated with
the tag (that contains the version bump) as the release notes by writing
them to a file and then specifying the file as the `body_path` of
`softprops/action-gh-release@v2`.
2026-01-06 15:02:39 -08:00
sayan-oai
54ded1a3c0 add web_search_cached flag (#8795)
Add `web_search_cached` feature to config. Enables `web_search` tool
with access only to cached/indexed results (see
[docs](https://platform.openai.com/docs/guides/tools-web-search#live-internet-access)).

This takes precedence over the existing `web_search_request`, which
continues to enable `web_search` over live results as it did before.

`web_search_cached` is disabled for review mode, as `web_search_request`
is.
2026-01-06 14:53:59 -08:00
Celia Chen
11d4f3f45e [app-server] fix config loading for conversations (#8765)
Currently we don't load config properly for app server conversations.
see:
https://linear.app/openai/issue/CODEX-3956/config-flags-not-respected-in-codex-app-server.
This PR fixes that by respecting the config passed in.

Tested by running `cargo build -p codex-cli &&
RUST_LOG=codex_app_server=debug CODEX_BIN=target/debug/codex cargo run
-p codex-app-server-test-client -- \
--config
model_providers.mock_provider.base_url=\"http://localhost:4010/v2\" \
    --config model_provider=\"mock_provider\" \
    --config model_providers.mock_provider.name="hello" \
    send-message-v2 "hello"`
and verified that the mock_provider is called instead of default
provider.

#closes
https://linear.app/openai/issue/CODEX-3956/config-flags-not-respected-in-codex-app-server

---------

Co-authored-by: Michael Bolin <mbolin@openai.com>
2026-01-06 22:02:17 +00:00
Owen Lin
8b7ec31ba7 feat(app-server): thread/rollback API (#8454)
Add `thread/rollback` to app-server to support IDEs undo-ing the last N
turns of a thread.

For context, an IDE partner will be supporting an "undo" capability
where the IDE (the app-server client) will be responsible for reverting
the local changes made during the last turn. To support this well, we
also need a way to drop the last turn (or more generally, the last N
turns) from the agent's context. This is what `thread/rollback` does.

**Core idea**: A Thread rollback is represented as a persisted event
message (EventMsg::ThreadRollback) in the rollout JSONL file, not by
rewriting history. On resume, both the model's context (core replay) and
the UI turn list (app-server v2's thread history builder) apply these
markers so the pruned history is consistent across live conversations
and `thread/resume`.

Implementation notes:
- Rollback only affects agent context and appends to the rollout file;
clients are responsible for reverting files on disk.
- If a thread rollback is currently in progress, subsequent
`thread/rollback` calls are rejected.
- Because we use `CodexConversation::submit` and codex core tracks
active turns, returning an error on concurrent rollbacks is communicated
via an `EventMsg::Error` with a new variant
`CodexErrorInfo::ThreadRollbackFailed`. app-server watches for that and
sends the BAD_REQUEST RPC response.

Tests cover thread rollbacks in both core and app-server, including when
`num_turns` > existing turns (which clears all turns).

**Note**: this explicitly does **not** behave like `/undo` which we just
removed from the CLI, which does the opposite of what `thread/rollback`
does. `/undo` reverts local changes via ghost commits/snapshots and does
not modify the agent's context / conversation history.
2026-01-06 21:23:48 +00:00
jif-oai
188f79afee feat: drop agent bus and store the agent status in codex directly (#8788) 2026-01-06 19:44:39 +00:00
Josh McKinney
a0b2d03302 Clear copy pill background and add snapshot test (#8777)
### Motivation
- Fix a visual bug where transcript text could bleed through the
on-screen copy "pill" overlay.
- Ensure the copy affordance fully covers the underlying buffer so the
pill background is solid and consistent with styling.
- Document the approach in-code to make the background-clearing
rationale explicit.

### Description
- Clear the pill area before drawing by iterating `Rect::positions()`
and calling `cell.set_symbol(" ")` and `cell.set_style(base_style)` in
`render_copy_pill` in `transcript_copy_ui.rs`.
- Added an explanatory comment for why the pill background is explicitly
cleared.
- Added a unit test `copy_pill_clears_background` and committed the
corresponding snapshot file to validate the rendering behavior.

### Testing
- Ran `just fmt` (formatting completed; non-blocking environment warning
may appear).
- Ran `just fix -p codex-tui2` to apply lints/fixes (completed). 
- Ran `cargo test -p codex-tui2` and all tests passed (snapshot updated
and tests succeeded).

------
[Codex
Task](https://chatgpt.com/codex/tasks/task_i_695c9b23e9b8832997d5a457c4d83410)
2026-01-06 11:21:26 -08:00
xl-openai
4ce9d0aa7b suppress popups while browsing input history (#8772) 2026-01-06 11:13:21 -08:00
jif-oai
1dd1355df3 feat: agent controller (#8783)
Added an agent control plane that lets sessions spawn or message other
conversations via `AgentControl`.

`AgentBus` (core/src/agent/bus.rs) keeps track of the last known status
of a conversation.

ConversationManager now holds shared state behind an Arc so AgentControl
keeps only a weak back-reference, the goal is just to avoid explicit
cycle reference.

Follow-ups:
* Build a small tool in the TUI to be able to see every agent and send
manual message to each of them
* Handle approval requests in this TUI
* Add tools to spawn/communicate between agents (see related design)
* Define agent types
2026-01-06 19:08:02 +00:00
Javi
915352b10c feat: add analytics config setting (#8350) 2026-01-06 19:04:13 +00:00
jif-oai
740bf0e755 chore: clear background terminals on interrupt (#8786) 2026-01-06 19:01:07 +00:00
jif-oai
d1c6329c32 feat: forced tool tips (#8752)
Force an announcement tooltip in the CLI. This query the gh repo on this
[file](https://raw.githubusercontent.com/openai/codex/main/announcement_tip.toml)
which contains announcements in TOML looking like this:
```
# Example announcement tips for Codex TUI.
# Each [[announcements]] entry is evaluated in order; the last matching one is shown.
# Dates are UTC, formatted as YYYY-MM-DD. The from_date is inclusive and the to_date is exclusive.
# version_regex matches against the CLI version (env!("CARGO_PKG_VERSION")); omit to apply to all versions.
# target_app specify which app should display the announcement (cli, vsce, ...).

[[announcements]]
content = "Welcome to Codex! Check out the new onboarding flow."
from_date = "2024-10-01"
to_date = "2024-10-15"
version_regex = "^0\\.0\\.0$"
target_app = "cli"
``` 

To make this efficient, the announcement is queried on a best effort
basis at the launch of the CLI (no refresh made after this).
This is done in an async way and we display the announcement (with 100%
probability) iff the announcement is available, the cache is correctly
warmed and there is a matching announcement (matching is recomputed for
each new session).
2026-01-06 18:02:05 +00:00
Owen Lin
cab7136fb3 chore: add model/list call to app-server-test-client (#8331)
Allows us to run `cargo run -p codex-app-server-test-client --
model-list` to return the list of models over app-server.
2026-01-06 17:50:17 +00:00
jif-oai
32db8ea5ca feat: add head-tail buffer for unified_exec (#8735) 2026-01-06 15:48:44 +00:00
Abdelkader Boudih
06e21c7a65 fix: update model examples to gpt-5.2 (#8566)
The models are outdated and sometime get used by GPT when it to try
delegate.

I have read the CLA Document and I hereby sign the CLA
2026-01-06 08:47:29 -07:00
Michael Bolin
7ecd0dc9b3 fix: stop honoring CODEX_MANAGED_CONFIG_PATH environment variable in production (#8762) 2026-01-06 07:10:27 -08:00
jif-oai
8858012fd1 chore: emit unified exec begin only when PTY exist (#8780) 2026-01-06 13:12:54 +00:00
Thibault Sottiaux
6346e4f560 fix: fix readiness subscribe token wrap-around (#8770)
Fixes ReadinessFlag::subscribe to avoid handing out token 0 or duplicate
tokens on i32 wrap-around, adds regression tests, and prevents readiness
gates from getting stuck waiting on an unmarkable or mis-authorized
token.
2026-01-06 13:09:02 +00:00
Josh McKinney
4c3d2a5bbe fix: render cwd-relative paths in tui (#8771)
Display paths relative to the cwd before checking git roots so view
image tool calls keep project-local names in jj/no-.git workspaces.
2026-01-06 03:17:40 +00:00
Josh McKinney
c92dbea7c1 tui2: stop baking streaming wraps; reflow agent markdown (#8761)
Background
Streaming assistant prose in tui2 was being rendered with viewport-width
wrapping during streaming, then stored in history cells as already split
`Line`s. Those width-derived breaks became indistinguishable from hard
newlines, so the transcript could not "un-split" on resize. This also
degraded copy/paste, since soft wraps looked like hard breaks.

What changed
- Introduce width-agnostic `MarkdownLogicalLine` output in
`tui2/src/markdown_render.rs`, preserving markdown wrap semantics:
initial/subsequent indents, per-line style, and a preformatted flag.
- Update the streaming collector (`tui2/src/markdown_stream.rs`) to emit
logical lines (newline-gated) and remove any captured viewport width.
- Update streaming orchestration (`tui2/src/streaming/*`) to queue and
emit logical lines, producing `AgentMessageCell::new_logical(...)`.
- Make `AgentMessageCell` store logical lines and wrap at render time in
`HistoryCell::transcript_lines_with_joiners(width)`, emitting joiners so
copy/paste can join soft-wrap continuations correctly.

Overlay deferral
When an overlay is active, defer *cells* (not rendered `Vec<Line>`) and
render them at overlay close time. This avoids baking width-derived
wraps based on a stale width.

Tests + docs
- Add resize/reflow regression tests + snapshots for streamed agent
output.
- Expand module/API docs for the new logical-line streaming pipeline and
clarify joiner semantics.
- Align scrollback-related docs/comments with current tui2 behavior
(main draw loop does not flush queued "history lines" to the terminal).

More details
See `codex-rs/tui2/docs/streaming_wrapping_design.md` for the full
problem statement and solution approach, and
`codex-rs/tui2/docs/tui_viewport_and_history.md` for viewport vs printed
output behavior.
2026-01-05 18:37:58 -08:00
Thibault Sottiaux
771f1ca6ab fix: accept whitespace-padded patch markers (#8746)
Trim whitespace when validating '*** Begin Patch'/'*** End Patch'
markers in codex-apply-patch so padded marker lines parse as intended,
and add regression coverage (unit + fixture scenario); this avoids
apply_patch failures when models include extra spacing. Tested with
cargo test -p codex-apply-patch.
2026-01-05 17:41:23 -08:00
Dylan Hurd
b1c93e135b chore(apply-patch) additional scenarios (#8230)
## Summary
More apply-patch scenarios

## Testing
- [x] This pr only adds tests
2026-01-05 15:56:38 -08:00
Curtis 'Fjord' Hawthorne
5f8776d34d Allow global exec flags after resume and fix CI codex build/timeout (#8440)
**Motivation**
- Bring `codex exec resume` to parity with top‑level flags so global
options (git check bypass, json, model, sandbox toggles) work after the
subcommand, including when outside a git repo.

**Description**
- Exec CLI: mark `--skip-git-repo-check`, `--json`, `--model`,
`--full-auto`, and `--dangerously-bypass-approvals-and-sandbox` as
global so they’re accepted after `resume`.
- Tests: add `exec_resume_accepts_global_flags_after_subcommand` to
verify those flags work when passed after `resume`.

**Testing**
- `just fmt`
- `cargo test -p codex-exec` (pass; ran with elevated perms to allow
network/port binds)
- Manual: exercised `codex exec resume` with global flags after the
subcommand to confirm behavior.
2026-01-05 22:12:09 +00:00
xl-openai
58a91a0b50 Use ConfigLayerStack for skills discovery. (#8497)
Use ConfigLayerStack to get all folders while loading skills.
2026-01-05 13:47:39 -08:00
Matthew Zeng
c29afc0cf3 [device-auth] Update login instruction for headless environments. (#8753)
We've seen reports that people who try to login on a remote/headless
machine will open the login link on their own machine and got errors.
Update the instructions to ask those users to use `codex login
--device-auth` instead.

<img width="1434" height="938" alt="CleanShot 2026-01-05 at 11 35 02@2x"
src="https://github.com/user-attachments/assets/2b209953-6a42-4eb0-8b55-bb0733f2e373"
/>
2026-01-05 13:46:42 -08:00
Michael Bolin
cafb07fe6e feat: add justification arg to prefix_rule() in *.rules (#8751)
Adds an optional `justification` parameter to the `prefix_rule()`
execpolicy DSL so policy authors can attach human-readable rationale to
a rule. That justification is propagated through parsing/matching and
can be surfaced to the model (or approval UI) when a command is blocked
or requires approval.

When a command is rejected (or gated behind approval) due to policy, a
generic message makes it hard for the model/user to understand what went
wrong and what to do instead. Allowing policy authors to supply a short
justification improves debuggability and helps guide the model toward
compliant alternatives.

Example:

```python
prefix_rule(
    pattern = ["git", "push"],
    decision = "forbidden",
    justification = "pushing is blocked in this repo",
)
```

If Codex tried to run `git push origin main`, now the failure would
include:

```
`git push origin main` rejected: pushing is blocked in this repo
```

whereas previously, all it was told was:

```
execpolicy forbids this command
```
2026-01-05 21:24:48 +00:00
iceweasel-oai
07f077dfb3 best effort to "hide" Sandbox users (#8492)
The elevated sandbox creates two new Windows users - CodexSandboxOffline
and CodexSandboxOnline. This is necessary, so this PR does all that it
can to "hide" those users. It uses the registry plus directory flags (on
their home directories) to get them to show up as little as possible.
2026-01-05 12:29:10 -08:00
Abrar Ahmed
7cf6f1c723 Use issuer URL in device auth prompt link (#7858)
## Summary

When using device-code login with a custom issuer
(`--experimental_issuer`), Codex correctly uses that issuer for the auth
flow — but the **terminal prompt still told users to open the default
OpenAI device URL** (`https://auth.openai.com/codex/device`). That’s
confusing and can send users to the **wrong domain** (especially for
enterprise/staging issuers). This PR updates the prompt (and related
URLs) to consistently use the configured issuer. 🎯

---

## 🔧 What changed

* 🔗 **Device auth prompt link** now uses the configured issuer (instead
of a hard-coded OpenAI URL)
* 🧭 **Redirect callback URL** is derived from the same issuer for
consistency
* 🧼 Minor cleanup: normalize the issuer base URL once and reuse it
(avoids formatting quirks like trailing `/`)

---

## 🧪 Repro + Before/After

### ▶️ Command

```bash
codex login --device-auth --experimental_issuer https://auth.example.com
```

###  Before (wrong link shown)

```text
1. Open this link in your browser and sign in to your account
   https://auth.openai.com/codex/device
```

###  After (correct link shown)

```text
1. Open this link in your browser and sign in to your account
   https://auth.example.com/codex/device
```

Full example output (same as before, but with the correct URL):

```text
Welcome to Codex [v0.72.0]
OpenAI's command-line coding agent

Follow these steps to sign in with ChatGPT using device code authorization:

1. Open this link in your browser and sign in to your account
   https://auth.example.com/codex/device

2. Enter this one-time code (expires in 15 minutes)
   BUT6-0M8K4

Device codes are a common phishing target. Never share this code.
```

---

##  Test plan

* 🟦 `codex login --device-auth` (default issuer): output remains
unchanged
* 🟩 `codex login --device-auth --experimental_issuer
https://auth.example.com`:

  * prompt link points to the issuer 
  * callback URL is derived from the same issuer 
  * no double slashes / mismatched domains 

Co-authored-by: Eric Traut <etraut@openai.com>
2026-01-05 13:09:05 -07:00
Gav Verma
57f8158608 chore: improve skills render section (#8459)
This change improves the skills render section
- Separate the skills list from usage rules with clear subheadings
- Define skill more clearly upfront
- Remove confusing trigger/discovery wording and make reference-following guidance more actionable
2026-01-05 11:55:26 -08:00
iceweasel-oai
95580f229e never let sandbox write to .codex/ or .codex/.sandbox/ (#8683)
Never treat .codex or .codex/.sandbox as a workspace root.
Handle write permissions to .codex/.sandbox in a single method so that
the sandbox setup/runner can write logs and other setup files to that
directory.
2026-01-05 11:54:21 -08:00
iceweasel-oai
720fa67816 better idempotency for creating/updating firewall rules during setup. (#8686)
make sure if the Sandbox has to re-initialize with different Sandbox
user SID, it still finds/updates the firewall rule instead of creating a
new one.
2026-01-05 10:42:33 -08:00
jif-oai
fabb797097 chore: GH pager (#8747) 2026-01-05 18:40:34 +00:00
Anton Panasenko
807f8a43c2 feat: expose outputSchema to user_turn/turn_start app_server API (#8377)
What changed
- Added `outputSchema` support to the app-server APIs, mirroring `codex
exec --output-schema` behavior.
- V1 `sendUserTurn` now accepts `outputSchema` and constrains the final
assistant message for that turn.
- V2 `turn/start` now accepts `outputSchema` and constrains the final
assistant message for that turn (explicitly per-turn only).

Core behavior
- `Op::UserTurn` already supported `final_output_json_schema`; now V1
`sendUserTurn` forwards `outputSchema` into that field.
- `Op::UserInput` now carries `final_output_json_schema` for per-turn
settings updates; core maps it into
`SessionSettingsUpdate.final_output_json_schema` so it applies to the
created turn context.
- V2 `turn/start` does NOT persist the schema via `OverrideTurnContext`
(it’s applied only for the current turn). Other overrides
(cwd/model/etc) keep their existing persistent behavior.

API / docs
- `codex-rs/app-server-protocol/src/protocol/v1.rs`: add `output_schema:
Option<serde_json::Value>` to `SendUserTurnParams` (serialized as
`outputSchema`).
- `codex-rs/app-server-protocol/src/protocol/v2.rs`: add `output_schema:
Option<JsonValue>` to `TurnStartParams` (serialized as `outputSchema`).
- `codex-rs/app-server/README.md`: document `outputSchema` for
`turn/start` and clarify it applies only to the current turn.
- `codex-rs/docs/codex_mcp_interface.md`: document `outputSchema` for v1
`sendUserTurn` and v2 `turn/start`.

Tests added/updated
- New app-server integration tests asserting `outputSchema` is forwarded
into outbound `/responses` requests as `text.format`:
  - `codex-rs/app-server/tests/suite/output_schema.rs`
  - `codex-rs/app-server/tests/suite/v2/output_schema.rs`
- Added per-turn semantics tests (schema does not leak to the next
turn):
  - `send_user_turn_output_schema_is_per_turn_v1`
  - `turn_start_output_schema_is_per_turn_v2`
- Added protocol wire-compat tests for the merged op:
  - serialize omits `final_output_json_schema` when `None`
  - deserialize works when field is missing
  - serialize includes `final_output_json_schema` when `Some(schema)`

Call site updates (high level)
- Updated all `Op::UserInput { .. }` constructions to include
`final_output_json_schema`:
  - `codex-rs/app-server/src/codex_message_processor.rs`
  - `codex-rs/core/src/codex_delegate.rs`
  - `codex-rs/mcp-server/src/codex_tool_runner.rs`
  - `codex-rs/tui/src/chatwidget.rs`
  - `codex-rs/tui2/src/chatwidget.rs`
  - plus impacted core tests.

Validation
- `just fmt`
- `cargo test -p codex-core`
- `cargo test -p codex-app-server`
- `cargo test -p codex-mcp-server`
- `cargo test -p codex-tui`
- `cargo test -p codex-tui2`
- `cargo test -p codex-protocol`
- `cargo clippy --all-features --tests --profile dev --fix -- -D
warnings`
2026-01-05 10:27:00 -08:00
gt-oai
1d8e2b4da8 (MacOS) Load config requirements from MDM (#8743)
Load managed requirements from MDM key `requirements_toml_base64`.

Tested on my Mac (using `defaults` to set the preference, though this
would be set by MDM in production):

```
➜  codex git:(gt/mdm-requirements) defaults read com.openai.codex requirements_toml_base64 | base64 -d
allowed_approval_policies = ["on-request"]

➜  codex git:(gt/mdm-requirements) just c --yolo
cargo run --bin codex -- "$@"
    Finished `dev` profile [unoptimized + debuginfo] target(s) in 0.26s
     Running `target/debug/codex --yolo`
Error loading configuration: value `Never` is not in the allowed set [OnRequest]
error: Recipe `codex` failed on line 11 with exit code 1

➜  codex git:(gt/mdm-requirements) defaults delete com.openai.codex requirements_toml_base64

➜  codex git:(gt/mdm-requirements) just c --yolo
cargo run --bin codex -- "$@"
    Finished `dev` profile [unoptimized + debuginfo] target(s) in 0.24s
     Running `target/debug/codex --yolo`
╭──────────────────────────────────────────────────────────╮
│ >_ OpenAI Codex (v0.0.0)                                 │
│                                                          │
│ model:     codex-auto-balanced medium   /model to change │
│ directory: ~/code/codex/codex-rs                         │
╰──────────────────────────────────────────────────────────╯

  Tip: Start a fresh idea with /new; the previous session stays in history.
```
2026-01-05 17:41:27 +00:00
Josh McKinney
bba5e5e0d4 fix(codex-api): handle Chat Completions DONE sentinel (#8708)
Context
- This code parses Server-Sent Events (SSE) from the legacy Chat
Completions streaming API (wire_api = "chat").
- The upstream protocol terminates a stream with a final sentinel event:
data: [DONE].
- Some of our test stubs/helpers historically end the stream with data:
DONE (no brackets).

How this was found
- GitHub Actions on Windows failed in codex-app-server integration tests
with wiremock verification errors (expected multiple POSTs, got 1).

Diagnosis
- The job logs included: codex_api::sse::chat: Failed to parse
ChatCompletions SSE event ... data: DONE.
- eventsource_stream surfaces the sentinel as a normal SSE event; it
does not automatically close the stream.
- The parser previously attempted to JSON-decode every data: payload.
The sentinel is not JSON, so we logged and skipped it, then continued
polling.
- On servers that keep the HTTP connection open after emitting the
sentinel (notably wiremock on Windows), skipping the sentinel meant we
never emitted ResponseEvent::Completed.
- Higher layers wait for completion before progressing (emitting
approval requests and issuing follow-up model calls), so the test never
reached the subsequent requests and wiremock panicked when its
expected-call count was not met.

Fix
- Treat both data: [DONE] and data: DONE as explicit end-of-stream
sentinels.
- When a sentinel is seen, flush any pending assistant/reasoning items
and emit ResponseEvent::Completed once.

Tests
- Add a regression unit test asserting we complete on the sentinel even
if the underlying connection is not closed.
2026-01-05 09:29:42 -08:00
Josh McKinney
8f10d3bf05 feat(tui2): transcript scrollbar (auto-hide + drag) (#8728)
## Summary
- Add a transcript scrollbar in `tui2` using `tui-scrollbar`.
- Reserve 2 columns on the right (1 empty gap + 1 scrollbar track) and
plumb the reduced width through wrapping/selection/copy so rendering and
interactions match.
- Auto-hide the scrollbar when the transcript is pinned to the bottom
(columns remain reserved).
- Add mouse click/drag support for the scrollbar, with pointer-capture
so drags don’t fall through into transcript selection.
- Skip scrollbar hit-testing when auto-hidden to avoid an invisible
interactive region.

## Notes
- Styling is theme-aware: in light themes the thumb is darker than the
track; in dark themes it reads as an “indented” element without going
full-white.
- Pre-Ratatui 0.30 (ratatui-core split) requires a small scratch-buffer
bridge; this should simplify once we move to Ratatui 0.30.

## Testing
- `just fmt`
- `just fix -p codex-tui2 --allow-no-vcs`
- `cargo test -p codex-tui2`
2026-01-05 09:05:14 -08:00
Gabriel Peal
468ee8a75c [MCP] Sanitize MCP tool names to ensure they are compatible with the Responses APO (#8694)
The Responses API requires that all tool names conform to
'^[a-zA-Z0-9_-]+$'. This PR replaces all non-conforming characters with
`_` to ensure that they can be used.

Fixes #8174
2026-01-05 09:00:45 -08:00
Thibault Sottiaux
0b53aed2d0 fix: /review to respect session cwd (#8738)
Fixes /review base-branch prompt resolution to use the session/turn cwd
(respecting runtime cwd overrides) so merge-base/diff guidance is
computed from the intended repo; adds a regression test for cwd
overrides; tested with cargo test -p codex-core --test all
review_uses_overridden_cwd_for_base_branch_merge_base.
2026-01-05 12:11:20 +00:00
jif-oai
649badd102 fix: chat multiple tool calls (#8556)
Fix this: https://github.com/openai/codex/issues/8479

The issue is that chat completion API expect all the tool calls in a
single assistant message and then all the tool call output in a single
response message
2026-01-05 10:37:43 +00:00
dependabot[bot]
a8e0fe8bb9 chore(deps): bump insta from 1.44.3 to 1.46.0 in /codex-rs (#8725)
Bumps [insta](https://github.com/mitsuhiko/insta) from 1.44.3 to 1.46.0.
<details>
<summary>Release notes</summary>
<p><em>Sourced from <a
href="https://github.com/mitsuhiko/insta/releases">insta's
releases</a>.</em></p>
<blockquote>
<h2>1.46.0</h2>
<h2>Release Notes</h2>
<ul>
<li>Add <code>INSTA_PENDING_DIR</code> environment variable for Bazel
and other hermetic build systems. When set, pending snapshots are
written to a separate directory while keeping the source tree read-only.
We are very open to feedback on this feature. <a
href="https://redirect.github.com/mitsuhiko/insta/issues/852">#852</a></li>
<li>Fix documentation for <code>test.runner_fallback</code> config key.
<a
href="https://redirect.github.com/mitsuhiko/insta/issues/853">#853</a></li>
</ul>
<h2>Install cargo-insta 1.46.0</h2>
<h3>Install prebuilt binaries via shell script</h3>
<pre lang="sh"><code>curl --proto '=https' --tlsv1.2 -LsSf
https://github.com/mitsuhiko/insta/releases/download/1.46.0/cargo-insta-installer.sh
| sh
</code></pre>
<h3>Install prebuilt binaries via powershell script</h3>
<pre lang="sh"><code>powershell -ExecutionPolicy Bypass -c &quot;irm
https://github.com/mitsuhiko/insta/releases/download/1.46.0/cargo-insta-installer.ps1
| iex&quot;
</code></pre>
<h2>Download cargo-insta 1.46.0</h2>
<table>
<thead>
<tr>
<th>File</th>
<th>Platform</th>
<th>Checksum</th>
</tr>
</thead>
<tbody>
<tr>
<td><a
href="https://github.com/mitsuhiko/insta/releases/download/1.46.0/cargo-insta-aarch64-apple-darwin.tar.xz">cargo-insta-aarch64-apple-darwin.tar.xz</a></td>
<td>Apple Silicon macOS</td>
<td><a
href="https://github.com/mitsuhiko/insta/releases/download/1.46.0/cargo-insta-aarch64-apple-darwin.tar.xz.sha256">checksum</a></td>
</tr>
<tr>
<td><a
href="https://github.com/mitsuhiko/insta/releases/download/1.46.0/cargo-insta-x86_64-apple-darwin.tar.xz">cargo-insta-x86_64-apple-darwin.tar.xz</a></td>
<td>Intel macOS</td>
<td><a
href="https://github.com/mitsuhiko/insta/releases/download/1.46.0/cargo-insta-x86_64-apple-darwin.tar.xz.sha256">checksum</a></td>
</tr>
<tr>
<td><a
href="https://github.com/mitsuhiko/insta/releases/download/1.46.0/cargo-insta-x86_64-pc-windows-msvc.zip">cargo-insta-x86_64-pc-windows-msvc.zip</a></td>
<td>x64 Windows</td>
<td><a
href="https://github.com/mitsuhiko/insta/releases/download/1.46.0/cargo-insta-x86_64-pc-windows-msvc.zip.sha256">checksum</a></td>
</tr>
<tr>
<td><a
href="https://github.com/mitsuhiko/insta/releases/download/1.46.0/cargo-insta-x86_64-unknown-linux-gnu.tar.xz">cargo-insta-x86_64-unknown-linux-gnu.tar.xz</a></td>
<td>x64 Linux</td>
<td><a
href="https://github.com/mitsuhiko/insta/releases/download/1.46.0/cargo-insta-x86_64-unknown-linux-gnu.tar.xz.sha256">checksum</a></td>
</tr>
<tr>
<td><a
href="https://github.com/mitsuhiko/insta/releases/download/1.46.0/cargo-insta-x86_64-unknown-linux-musl.tar.xz">cargo-insta-x86_64-unknown-linux-musl.tar.xz</a></td>
<td>x64 MUSL Linux</td>
<td><a
href="https://github.com/mitsuhiko/insta/releases/download/1.46.0/cargo-insta-x86_64-unknown-linux-musl.tar.xz.sha256">checksum</a></td>
</tr>
</tbody>
</table>
<h2>1.45.1</h2>
<h2>Release Notes</h2>
<ul>
<li>Fix backward compatibility with TOML format produced by insta &lt;
1.45.0. <a
href="https://redirect.github.com/mitsuhiko/insta/issues/849">#849</a>
(<a
href="https://github.com/chitoku-k"><code>@​chitoku-k</code></a>)</li>
</ul>
<h2>Install cargo-insta 1.45.1</h2>
<h3>Install prebuilt binaries via shell script</h3>
<pre lang="sh"><code>curl --proto '=https' --tlsv1.2 -LsSf
https://github.com/mitsuhiko/insta/releases/download/1.45.1/cargo-insta-installer.sh
| sh
</code></pre>
<h3>Install prebuilt binaries via powershell script</h3>
<pre lang="sh"><code>powershell -ExecutionPolicy Bypass -c &quot;irm
https://github.com/mitsuhiko/insta/releases/download/1.45.1/cargo-insta-installer.ps1
| iex&quot;
</code></pre>
<h2>Download cargo-insta 1.45.1</h2>
<!-- raw HTML omitted -->
</blockquote>
<p>... (truncated)</p>
</details>
<details>
<summary>Changelog</summary>
<p><em>Sourced from <a
href="https://github.com/mitsuhiko/insta/blob/master/CHANGELOG.md">insta's
changelog</a>.</em></p>
<blockquote>
<h2>1.46.0</h2>
<ul>
<li>Add <code>INSTA_PENDING_DIR</code> environment variable for Bazel
and other hermetic build systems. When set, pending snapshots are
written to a separate directory while keeping the source tree read-only.
We are very open to feedback on this feature. <a
href="https://redirect.github.com/mitsuhiko/insta/issues/852">#852</a></li>
<li>Fix documentation for <code>test.runner_fallback</code> config key.
<a
href="https://redirect.github.com/mitsuhiko/insta/issues/853">#853</a></li>
</ul>
<h2>1.45.1</h2>
<ul>
<li>Fix backward compatibility with TOML format produced by insta &lt;
1.45.0. <a
href="https://redirect.github.com/mitsuhiko/insta/issues/849">#849</a>
(<a
href="https://github.com/chitoku-k"><code>@​chitoku-k</code></a>)</li>
</ul>
<h2>1.45.0</h2>
<ul>
<li>Add external diff tool support via <code>INSTA_DIFF_TOOL</code>
environment variable. When set, insta uses the specified tool (e.g.,
<code>delta</code>, <code>difftastic</code>) to display snapshot diffs
instead of the built-in diff. The tool is invoked as <code>&lt;tool&gt;
&lt;old_file&gt; &lt;new_file&gt;</code>. <a
href="https://redirect.github.com/mitsuhiko/insta/issues/844">#844</a></li>
<li>Add <code>test.disable_nextest_doctest</code> config option to
<code>insta.yaml</code>, allowing users to silence the nextest doctest
warning via config instead of passing <code>--dnd</code> every time. <a
href="https://redirect.github.com/mitsuhiko/insta/issues/842">#842</a></li>
<li>Skip non-insta snapshot files in unreferenced detection. Projects
using both insta and other snapshot tools (like vitest or jest) can now
use <code>--unreferenced=reject</code> without false positives on
<code>.snap</code> files from other tools. <a
href="https://redirect.github.com/mitsuhiko/insta/issues/846">#846</a></li>
<li>Collect warnings from tests for display after run. Ensures
deprecation warnings are visible even when nextest suppresses
stdout/stderr from passing tests. <a
href="https://redirect.github.com/mitsuhiko/insta/issues/840">#840</a></li>
<li>Update TOML serialization to be up-to-date and backwards-compatible.
<a
href="https://redirect.github.com/mitsuhiko/insta/issues/834">#834</a>
(<a href="https://github.com/spoutn1k"><code>@​spoutn1k</code></a>)</li>
<li>Support <code>clippy::needless_raw_strings</code> lint by only using
raw strings when content contains backslashes or quotes. <a
href="https://redirect.github.com/mitsuhiko/insta/issues/828">#828</a></li>
</ul>
</blockquote>
</details>
<details>
<summary>Commits</summary>
<ul>
<li><a
href="7d27e3ab04"><code>7d27e3a</code></a>
Release 1.46.0 (<a
href="https://redirect.github.com/mitsuhiko/insta/issues/855">#855</a>)</li>
<li><a
href="3aa59d6f94"><code>3aa59d6</code></a>
Add INSTA_PENDING_DIR environment variable for hermetic builds (<a
href="https://redirect.github.com/mitsuhiko/insta/issues/852">#852</a>)</li>
<li><a
href="fd40cf7d51"><code>fd40cf7</code></a>
Fix docs for test.runner_fallback config key (<a
href="https://redirect.github.com/mitsuhiko/insta/issues/853">#853</a>)</li>
<li><a
href="ac191ba67c"><code>ac191ba</code></a>
Add test for multiline snapshots without special characters (<a
href="https://redirect.github.com/mitsuhiko/insta/issues/848">#848</a>)</li>
<li><a
href="55f6999fd4"><code>55f6999</code></a>
Release 1.45.1 (<a
href="https://redirect.github.com/mitsuhiko/insta/issues/850">#850</a>)</li>
<li><a
href="00b1cd4ef0"><code>00b1cd4</code></a>
Fix backward compatibility with TOML format produced by insta &lt;
1.45.0 (<a
href="https://redirect.github.com/mitsuhiko/insta/issues/849">#849</a>)</li>
<li><a
href="681a02612b"><code>681a026</code></a>
Release 1.45.0 (<a
href="https://redirect.github.com/mitsuhiko/insta/issues/847">#847</a>)</li>
<li><a
href="ad233cd21b"><code>ad233cd</code></a>
Skip non-insta snapshot files in unreferenced detection (<a
href="https://redirect.github.com/mitsuhiko/insta/issues/846">#846</a>)</li>
<li><a
href="d8e8dfe7aa"><code>d8e8dfe</code></a>
Collect warnings from tests for display after run (<a
href="https://redirect.github.com/mitsuhiko/insta/issues/840">#840</a>)</li>
<li><a
href="521812cb86"><code>521812c</code></a>
Support clippy::needless_raw_strings lint (<a
href="https://redirect.github.com/mitsuhiko/insta/issues/828">#828</a>)</li>
<li>Additional commits viewable in <a
href="https://github.com/mitsuhiko/insta/compare/1.44.3...1.46.0">compare
view</a></li>
</ul>
</details>
<br />


[![Dependabot compatibility
score](https://dependabot-badges.githubapp.com/badges/compatibility_score?dependency-name=insta&package-manager=cargo&previous-version=1.44.3&new-version=1.46.0)](https://docs.github.com/en/github/managing-security-vulnerabilities/about-dependabot-security-updates#about-compatibility-scores)

Dependabot will resolve any conflicts with this PR as long as you don't
alter it yourself. You can also trigger a rebase manually by commenting
`@dependabot rebase`.

[//]: # (dependabot-automerge-start)
[//]: # (dependabot-automerge-end)

---

<details>
<summary>Dependabot commands and options</summary>
<br />

You can trigger Dependabot actions by commenting on this PR:
- `@dependabot rebase` will rebase this PR
- `@dependabot recreate` will recreate this PR, overwriting any edits
that have been made to it
- `@dependabot merge` will merge this PR after your CI passes on it
- `@dependabot squash and merge` will squash and merge this PR after
your CI passes on it
- `@dependabot cancel merge` will cancel a previously requested merge
and block automerging
- `@dependabot reopen` will reopen this PR if it is closed
- `@dependabot close` will close this PR and stop Dependabot recreating
it. You can achieve the same result by closing it manually
- `@dependabot show <dependency name> ignore conditions` will show all
of the ignore conditions of the specified dependency
- `@dependabot ignore this major version` will close this PR and stop
Dependabot creating any more for this major version (unless you reopen
the PR or upgrade to it yourself)
- `@dependabot ignore this minor version` will close this PR and stop
Dependabot creating any more for this minor version (unless you reopen
the PR or upgrade to it yourself)
- `@dependabot ignore this dependency` will close this PR and stop
Dependabot creating any more for this dependency (unless you reopen the
PR or upgrade to it yourself)


</details>

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-01-04 23:51:20 -07:00
dependabot[bot]
e139ef3e67 chore(deps): bump derive_more from 2.0.1 to 2.1.1 in /codex-rs (#8724)
Bumps [derive_more](https://github.com/JelteF/derive_more) from 2.0.1 to
2.1.1.
<details>
<summary>Release notes</summary>
<p><em>Sourced from <a
href="https://github.com/JelteF/derive_more/releases">derive_more's
releases</a>.</em></p>
<blockquote>
<h2>2.1.1</h2>
<p><a href="https://docs.rs/derive_more/2.1.1">API docs</a>
<a
href="https://github.com/JelteF/derive_more/blob/v2.1.1/CHANGELOG.md#211---2025-12-22">Changelog</a></p>
<h2>2.1.0</h2>
<h3>Added</h3>
<ul>
<li>Support <code>#[display(rename_all =
&quot;&lt;casing&gt;&quot;)]</code> attribute to change output for
implicit naming of unit enum variants or unit structs when deriving
<code>Display</code>. (<a
href="https://redirect.github.com/JelteF/derive_more/pull/443">#443</a>)</li>
<li>Support <code>#[from_str(rename_all =
&quot;&lt;casing&gt;&quot;)]</code> attribute for unit enum variants and
unit structs when deriving <code>FromStr</code>. (<a
href="https://redirect.github.com/JelteF/derive_more/pull/467">#467</a>)</li>
<li>Support <code>Option</code> fields for <code>Error::source()</code>
in <code>Error</code> derive. (<a
href="https://redirect.github.com/JelteF/derive_more/pull/459">#459</a>)</li>
<li>Support structs with no fields in <code>FromStr</code> derive. (<a
href="https://redirect.github.com/JelteF/derive_more/pull/469">#469</a>)</li>
<li>Add <code>PartialEq</code> derive similar to <code>std</code>'s one,
but considering generics correctly, and implementing <code>ne()</code>
method as well. (<a
href="https://redirect.github.com/JelteF/derive_more/pull/473">#473</a>,
<a
href="https://redirect.github.com/JelteF/derive_more/pull/475">#475</a>)</li>
<li>Add <code>Eq</code> derive similar to <code>std</code>'s one, but
considering generics correctly. (<a
href="https://redirect.github.com/JelteF/derive_more/pull/479">#479</a>)</li>
<li>Proxy-pass <code>#[allow]</code>/<code>#[expect]</code> attributes
of the type in <code>Constructor</code> derive. (<a
href="https://redirect.github.com/JelteF/derive_more/pull/477">#477</a>)</li>
<li>Support <code>Deref</code> and <code>DerefMut</code> derives for
enums. (<a
href="https://redirect.github.com/JelteF/derive_more/pull/485">#485</a>)</li>
<li>Support custom error in <code>FromStr</code> derive. (<a
href="https://redirect.github.com/JelteF/derive_more/pull/494">#494</a>)</li>
<li>Support custom error in <code>TryInto</code> derive. (<a
href="https://redirect.github.com/JelteF/derive_more/pull/503">#503</a>)</li>
<li>Support skipping fields in <code>Add</code>-like,
<code>AddAssign</code>-like, <code>Mul</code>-like and
<code>MulAssign</code>-like derives. (<a
href="https://redirect.github.com/JelteF/derive_more/pull/472">#472</a>)</li>
</ul>
<h3>Changed</h3>
<ul>
<li>The minimum supported Rust version (MSRV) is now Rust 1.81. (<a
href="https://redirect.github.com/JelteF/derive_more/pull/466">#466</a>)</li>
<li><code>Add</code>-like, <code>AddAssign</code>-like,
<code>Mul</code>-like and <code>MulAssign</code>-like derives now infer
trait bounds for generics structurally (bound field types instead of
type parameters directly). (<a
href="https://redirect.github.com/JelteF/derive_more/pull/472">#472</a>)</li>
</ul>
<h3>Fixed</h3>
<ul>
<li>Suppress deprecation warnings in generated code. (<a
href="https://redirect.github.com/JelteF/derive_more/pull/454">#454</a>)</li>
<li>Silent no-op when <code>#[try_from(repr)]</code> attribute is not
specified for <code>TryFrom</code> derive. (<a
href="https://redirect.github.com/JelteF/derive_more/pull/458">#458</a>)</li>
<li>Missing trait bounds in <code>AsRef</code>/<code>AsMut</code>
derives when associative types are involved. (<a
href="https://redirect.github.com/JelteF/derive_more/pull/474">#474</a>)</li>
<li>Erroneous code generated in <code>Try</code>/<code>TryInto</code>
derives when <code>Self</code> type is present in the struct or enum
definition. (<a
href="https://redirect.github.com/JelteF/derive_more/pull/489">#489</a>)</li>
<li>Dependency on unstable
<code>feature(error_generic_member_access)</code> in <code>Error</code>
derive when using <code>Backtrace</code> on a non-nightly toolchain. (<a
href="https://redirect.github.com/JelteF/derive_more/pull/513">#513</a>)</li>
<li>Broken support for <code>#[&lt;display-trait&gt;(&quot;default
formatting&quot;)]</code> attribute without <code>{_variant}</code>
being used as default for enum variants without explicit formatting. (<a
href="https://redirect.github.com/JelteF/derive_more/pull/495">#495</a>)</li>
</ul>
<h2>New Contributors</h2>
<ul>
<li><a href="https://github.com/jasl"><code>@​jasl</code></a> made their
first contribution in <a
href="https://redirect.github.com/JelteF/derive_more/pull/453">JelteF/derive_more#453</a></li>
<li><a href="https://github.com/aborgna-q"><code>@​aborgna-q</code></a>
made their first contribution in <a
href="https://redirect.github.com/JelteF/derive_more/pull/454">JelteF/derive_more#454</a></li>
<li><a
href="https://github.com/maxime-bruno"><code>@​maxime-bruno</code></a>
made their first contribution in <a
href="https://redirect.github.com/JelteF/derive_more/pull/461">JelteF/derive_more#461</a></li>
<li><a
href="https://github.com/Cheban1996"><code>@​Cheban1996</code></a> made
their first contribution in <a
href="https://redirect.github.com/JelteF/derive_more/pull/477">JelteF/derive_more#477</a></li>
<li><a href="https://github.com/kiendang"><code>@​kiendang</code></a>
made their first contribution in <a
href="https://redirect.github.com/JelteF/derive_more/pull/486">JelteF/derive_more#486</a></li>
<li><a
href="https://github.com/goldlinker"><code>@​goldlinker</code></a> made
their first contribution in <a
href="https://redirect.github.com/JelteF/derive_more/pull/509">JelteF/derive_more#509</a></li>
<li><a href="https://github.com/CJKay"><code>@​CJKay</code></a> made
their first contribution in <a
href="https://redirect.github.com/JelteF/derive_more/pull/513">JelteF/derive_more#513</a></li>
<li><a
href="https://github.com/ErmitaVulpe"><code>@​ErmitaVulpe</code></a>
made their first contribution in <a
href="https://redirect.github.com/JelteF/derive_more/pull/472">JelteF/derive_more#472</a></li>
</ul>
<p><strong>Full Changelog</strong>: <a
href="https://github.com/JelteF/derive_more/compare/v2.0.1...v2.1.0">https://github.com/JelteF/derive_more/compare/v2.0.1...v2.1.0</a></p>
</blockquote>
</details>
<details>
<summary>Changelog</summary>
<p><em>Sourced from <a
href="https://github.com/JelteF/derive_more/blob/master/CHANGELOG.md">derive_more's
changelog</a>.</em></p>
<blockquote>
<h2>2.1.1 - 2025-12-22</h2>
<h3>Fixed</h3>
<ul>
<li><code>.as_dyn_error()</code> method hygiene inside
<code>Error</code> derive expansion.
(<a
href="https://redirect.github.com/JelteF/derive_more/pull/527">#527</a>)</li>
</ul>
<h2>2.1.0 - 2025-12-02</h2>
<h3>Added</h3>
<ul>
<li>Support <code>#[display(rename_all =
&quot;&lt;casing&gt;&quot;)]</code> attribute to change output for
implicit naming of unit enum variants or unit structs when deriving
<code>Display</code>.
(<a
href="https://redirect.github.com/JelteF/derive_more/pull/443">#443</a>)</li>
<li>Support <code>#[from_str(rename_all =
&quot;&lt;casing&gt;&quot;)]</code> attribute for unit enum variants
and unit structs when deriving <code>FromStr</code>.
(<a
href="https://redirect.github.com/JelteF/derive_more/pull/467">#467</a>)</li>
<li>Support <code>Option</code> fields for <code>Error::source()</code>
in <code>Error</code> derive.
(<a
href="https://redirect.github.com/JelteF/derive_more/pull/459">#459</a>)</li>
<li>Support structs with no fields in <code>FromStr</code> derive.
(<a
href="https://redirect.github.com/JelteF/derive_more/pull/469">#469</a>)</li>
<li>Add <code>PartialEq</code> derive similar to <code>std</code>'s one,
but considering generics correctly,
and implementing <code>ne()</code> method as well.
(<a
href="https://redirect.github.com/JelteF/derive_more/pull/473">#473</a>,
<a
href="https://redirect.github.com/JelteF/derive_more/pull/475">#475</a>)</li>
<li>Add <code>Eq</code> derive similar to <code>std</code>'s one, but
considering generics correctly.
(<a
href="https://redirect.github.com/JelteF/derive_more/pull/479">#479</a>)</li>
<li>Proxy-pass <code>#[allow]</code>/<code>#[expect]</code> attributes
of the type in <code>Constructor</code> derive.
(<a
href="https://redirect.github.com/JelteF/derive_more/pull/477">#477</a>)</li>
<li>Support <code>Deref</code> and <code>DerefMut</code> derives for
enums.
(<a
href="https://redirect.github.com/JelteF/derive_more/pull/485">#485</a>)</li>
<li>Support custom error in <code>FromStr</code> derive.
(<a
href="https://redirect.github.com/JelteF/derive_more/pull/494">#494</a>)</li>
<li>Support custom error in <code>TryInto</code> derive.
(<a
href="https://redirect.github.com/JelteF/derive_more/pull/503">#503</a>)</li>
<li>Support skipping fields in <code>Add</code>-like,
<code>AddAssign</code>-like, <code>Mul</code>-like and
<code>MulAssign</code>-like derives.
(<a
href="https://redirect.github.com/JelteF/derive_more/pull/472">#472</a>)</li>
</ul>
<h3>Changed</h3>
<ul>
<li>The minimum supported Rust version (MSRV) is now Rust 1.81.
(<a
href="https://redirect.github.com/JelteF/derive_more/pull/466">#466</a>)</li>
<li><code>Add</code>-like, <code>AddAssign</code>-like,
<code>Mul</code>-like and <code>MulAssign</code>-like derives now
infer trait bounds for generics structurally (bound field types instead
of
type parameters directly).
(<a
href="https://redirect.github.com/JelteF/derive_more/pull/472">#472</a>)</li>
</ul>
<h3>Fixed</h3>
<!-- raw HTML omitted -->
</blockquote>
<p>... (truncated)</p>
</details>
<details>
<summary>Commits</summary>
<ul>
<li><a
href="f7bb41ac05"><code>f7bb41a</code></a>
Correct release date</li>
<li><a
href="289988ab67"><code>289988a</code></a>
Prepare 2.1.1 release (<a
href="https://redirect.github.com/JelteF/derive_more/issues/529">#529</a>)</li>
<li><a
href="d820ec6e3a"><code>d820ec6</code></a>
Fix <code>derive(Error)</code> expansion hygiene (<a
href="https://redirect.github.com/JelteF/derive_more/issues/527">#527</a>,
<a
href="https://redirect.github.com/JelteF/derive_more/issues/526">#526</a>)</li>
<li><a
href="5627282784"><code>5627282</code></a>
Remove glob imports in <code>derive(From)</code> tests to mend 1.94
nightly Rust regress...</li>
<li><a
href="c354bad7c9"><code>c354bad</code></a>
Prepare 2.1.0 release (<a
href="https://redirect.github.com/JelteF/derive_more/issues/521">#521</a>)</li>
<li><a
href="983875f8ab"><code>983875f</code></a>
Allow using enum-level attributes for non-<code>Display</code>
formatting traits as defa...</li>
<li><a
href="2d3805b66f"><code>2d3805b</code></a>
Allow skipping fields for
<code>Add</code>/<code>AddAssign</code>/<code>Mul</code>/<code>MulAssign</code>-like
derives (#...</li>
<li><a
href="1b5d314b8c"><code>1b5d314</code></a>
Upgrade <code>convert_case</code> requirement from 0.9 to 0.10 version
(<a
href="https://redirect.github.com/JelteF/derive_more/issues/520">#520</a>)</li>
<li><a
href="c32d0a0a05"><code>c32d0a0</code></a>
Upgrade <code>actions/checkout</code> from 5 to 6 version (<a
href="https://redirect.github.com/JelteF/derive_more/issues/519">#519</a>)</li>
<li><a
href="905f5a3020"><code>905f5a3</code></a>
Upgrade <code>convert_case</code> crate from 0.8 to 0.9 version (<a
href="https://redirect.github.com/JelteF/derive_more/issues/517">#517</a>)</li>
<li>Additional commits viewable in <a
href="https://github.com/JelteF/derive_more/compare/v2.0.1...v2.1.1">compare
view</a></li>
</ul>
</details>
<br />


[![Dependabot compatibility
score](https://dependabot-badges.githubapp.com/badges/compatibility_score?dependency-name=derive_more&package-manager=cargo&previous-version=2.0.1&new-version=2.1.1)](https://docs.github.com/en/github/managing-security-vulnerabilities/about-dependabot-security-updates#about-compatibility-scores)

Dependabot will resolve any conflicts with this PR as long as you don't
alter it yourself. You can also trigger a rebase manually by commenting
`@dependabot rebase`.

[//]: # (dependabot-automerge-start)
[//]: # (dependabot-automerge-end)

---

<details>
<summary>Dependabot commands and options</summary>
<br />

You can trigger Dependabot actions by commenting on this PR:
- `@dependabot rebase` will rebase this PR
- `@dependabot recreate` will recreate this PR, overwriting any edits
that have been made to it
- `@dependabot merge` will merge this PR after your CI passes on it
- `@dependabot squash and merge` will squash and merge this PR after
your CI passes on it
- `@dependabot cancel merge` will cancel a previously requested merge
and block automerging
- `@dependabot reopen` will reopen this PR if it is closed
- `@dependabot close` will close this PR and stop Dependabot recreating
it. You can achieve the same result by closing it manually
- `@dependabot show <dependency name> ignore conditions` will show all
of the ignore conditions of the specified dependency
- `@dependabot ignore this major version` will close this PR and stop
Dependabot creating any more for this major version (unless you reopen
the PR or upgrade to it yourself)
- `@dependabot ignore this minor version` will close this PR and stop
Dependabot creating any more for this minor version (unless you reopen
the PR or upgrade to it yourself)
- `@dependabot ignore this dependency` will close this PR and stop
Dependabot creating any more for this dependency (unless you reopen the
PR or upgrade to it yourself)


</details>

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-01-04 23:50:58 -07:00
dependabot[bot]
db1423ae8b chore(deps): bump tokio-stream from 0.1.17 to 0.1.18 in /codex-rs (#8723)
Bumps [tokio-stream](https://github.com/tokio-rs/tokio) from 0.1.17 to
0.1.18.
<details>
<summary>Commits</summary>
<ul>
<li><a
href="60b083b630"><code>60b083b</code></a>
chore: prepare tokio-stream 0.1.18 (<a
href="https://redirect.github.com/tokio-rs/tokio/issues/7830">#7830</a>)</li>
<li><a
href="9cc02cc88d"><code>9cc02cc</code></a>
chore: prepare tokio-util 0.7.18 (<a
href="https://redirect.github.com/tokio-rs/tokio/issues/7829">#7829</a>)</li>
<li><a
href="d2799d791b"><code>d2799d7</code></a>
task: improve the docs of <code>Builder::spawn_local</code> (<a
href="https://redirect.github.com/tokio-rs/tokio/issues/7828">#7828</a>)</li>
<li><a
href="4d4870f291"><code>4d4870f</code></a>
task: doc that task drops before JoinHandle completion (<a
href="https://redirect.github.com/tokio-rs/tokio/issues/7825">#7825</a>)</li>
<li><a
href="fdb150901a"><code>fdb1509</code></a>
fs: check for io-uring opcode support (<a
href="https://redirect.github.com/tokio-rs/tokio/issues/7815">#7815</a>)</li>
<li><a
href="426a562780"><code>426a562</code></a>
rt: remove <code>allow(dead_code)</code> after <code>JoinSet</code>
stabilization (<a
href="https://redirect.github.com/tokio-rs/tokio/issues/7826">#7826</a>)</li>
<li><a
href="e3b89bbefa"><code>e3b89bb</code></a>
chore: prepare Tokio v1.49.0 (<a
href="https://redirect.github.com/tokio-rs/tokio/issues/7824">#7824</a>)</li>
<li><a
href="4f577b84e9"><code>4f577b8</code></a>
Merge 'tokio-1.47.3' into 'master'</li>
<li><a
href="f320197693"><code>f320197</code></a>
chore: prepare Tokio v1.47.3 (<a
href="https://redirect.github.com/tokio-rs/tokio/issues/7823">#7823</a>)</li>
<li><a
href="ea6b144cd1"><code>ea6b144</code></a>
ci: freeze rustc on nightly-2025-01-25 in <code>netlify.toml</code> (<a
href="https://redirect.github.com/tokio-rs/tokio/issues/7652">#7652</a>)</li>
<li>Additional commits viewable in <a
href="https://github.com/tokio-rs/tokio/compare/tokio-stream-0.1.17...tokio-stream-0.1.18">compare
view</a></li>
</ul>
</details>
<br />


[![Dependabot compatibility
score](https://dependabot-badges.githubapp.com/badges/compatibility_score?dependency-name=tokio-stream&package-manager=cargo&previous-version=0.1.17&new-version=0.1.18)](https://docs.github.com/en/github/managing-security-vulnerabilities/about-dependabot-security-updates#about-compatibility-scores)

Dependabot will resolve any conflicts with this PR as long as you don't
alter it yourself. You can also trigger a rebase manually by commenting
`@dependabot rebase`.

[//]: # (dependabot-automerge-start)
[//]: # (dependabot-automerge-end)

---

<details>
<summary>Dependabot commands and options</summary>
<br />

You can trigger Dependabot actions by commenting on this PR:
- `@dependabot rebase` will rebase this PR
- `@dependabot recreate` will recreate this PR, overwriting any edits
that have been made to it
- `@dependabot merge` will merge this PR after your CI passes on it
- `@dependabot squash and merge` will squash and merge this PR after
your CI passes on it
- `@dependabot cancel merge` will cancel a previously requested merge
and block automerging
- `@dependabot reopen` will reopen this PR if it is closed
- `@dependabot close` will close this PR and stop Dependabot recreating
it. You can achieve the same result by closing it manually
- `@dependabot show <dependency name> ignore conditions` will show all
of the ignore conditions of the specified dependency
- `@dependabot ignore this major version` will close this PR and stop
Dependabot creating any more for this major version (unless you reopen
the PR or upgrade to it yourself)
- `@dependabot ignore this minor version` will close this PR and stop
Dependabot creating any more for this minor version (unless you reopen
the PR or upgrade to it yourself)
- `@dependabot ignore this dependency` will close this PR and stop
Dependabot creating any more for this dependency (unless you reopen the
PR or upgrade to it yourself)


</details>

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-01-04 23:50:37 -07:00
dependabot[bot]
1d678c8187 chore(deps): bump clap_complete from 4.5.57 to 4.5.64 in /codex-rs (#8721)
Bumps [clap_complete](https://github.com/clap-rs/clap) from 4.5.57 to
4.5.64.
<details>
<summary>Commits</summary>
<ul>
<li><a
href="e115243369"><code>e115243</code></a>
chore: Release</li>
<li><a
href="d4c34fa2b8"><code>d4c34fa</code></a>
docs: Update changelog</li>
<li><a
href="ab4f438860"><code>ab4f438</code></a>
Merge pull request <a
href="https://redirect.github.com/clap-rs/clap/issues/6203">#6203</a>
from jpgrayson/fix/zsh-space-after-dir-completions</li>
<li><a
href="5571b83c8a"><code>5571b83</code></a>
fix(complete): Trailing space after zsh directory completions</li>
<li><a
href="06a2311586"><code>06a2311</code></a>
chore: Release</li>
<li><a
href="bed131f7ae"><code>bed131f</code></a>
docs: Update changelog</li>
<li><a
href="a61c53e6dd"><code>a61c53e</code></a>
Merge pull request <a
href="https://redirect.github.com/clap-rs/clap/issues/6202">#6202</a>
from iepathos/6201-symlink-path-completions</li>
<li><a
href="c3b440570e"><code>c3b4405</code></a>
fix(complete): Follow symlinks in path completion</li>
<li><a
href="a794395340"><code>a794395</code></a>
test(complete): Add symlink path completion tests</li>
<li><a
href="ca0aeba31f"><code>ca0aeba</code></a>
chore: Release</li>
<li>Additional commits viewable in <a
href="https://github.com/clap-rs/clap/compare/clap_complete-v4.5.57...clap_complete-v4.5.64">compare
view</a></li>
</ul>
</details>
<br />


[![Dependabot compatibility
score](https://dependabot-badges.githubapp.com/badges/compatibility_score?dependency-name=clap_complete&package-manager=cargo&previous-version=4.5.57&new-version=4.5.64)](https://docs.github.com/en/github/managing-security-vulnerabilities/about-dependabot-security-updates#about-compatibility-scores)

Dependabot will resolve any conflicts with this PR as long as you don't
alter it yourself. You can also trigger a rebase manually by commenting
`@dependabot rebase`.

[//]: # (dependabot-automerge-start)
[//]: # (dependabot-automerge-end)

---

<details>
<summary>Dependabot commands and options</summary>
<br />

You can trigger Dependabot actions by commenting on this PR:
- `@dependabot rebase` will rebase this PR
- `@dependabot recreate` will recreate this PR, overwriting any edits
that have been made to it
- `@dependabot merge` will merge this PR after your CI passes on it
- `@dependabot squash and merge` will squash and merge this PR after
your CI passes on it
- `@dependabot cancel merge` will cancel a previously requested merge
and block automerging
- `@dependabot reopen` will reopen this PR if it is closed
- `@dependabot close` will close this PR and stop Dependabot recreating
it. You can achieve the same result by closing it manually
- `@dependabot show <dependency name> ignore conditions` will show all
of the ignore conditions of the specified dependency
- `@dependabot ignore this major version` will close this PR and stop
Dependabot creating any more for this major version (unless you reopen
the PR or upgrade to it yourself)
- `@dependabot ignore this minor version` will close this PR and stop
Dependabot creating any more for this minor version (unless you reopen
the PR or upgrade to it yourself)
- `@dependabot ignore this dependency` will close this PR and stop
Dependabot creating any more for this dependency (unless you reopen the
PR or upgrade to it yourself)


</details>

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-01-04 23:49:55 -07:00
Josh McKinney
181ff89cbd tui2: copy selection dismisses highlight (#8718)
Clicking the transcript copy pill or pressing the copy shortcut now
copies the selected transcript text and clears the highlight.

Show transient footer feedback ("Copied"/"Copy failed") after a copy
attempt, with logic in transcript_copy_action to keep app.rs smaller and
closer to tui for long-term diffs.

Update footer snapshots and add tiny unit tests for feedback expiry.


https://github.com/user-attachments/assets/c36c8163-11c5-476b-b388-e6fbe0ff6034
2026-01-04 22:05:18 -08:00
Josh McKinney
5678213058 fix(tui2): render copy pill at viewport bottom (#8716)
When the selection ends on the last visible row, the copy affordance had
no space below and never rendered. Fall back to placing it above (or on
the same row for 1-row viewports) and add a regression test.
2026-01-04 22:05:08 -08:00
Josh McKinney
279283fe02 fix(tui2): avoid scroll stickiness at cell boundaries (#8695)
Mouse/trackpad scrolling in tui2 applies deltas in visual lines, but the
transcript scroll state was anchored only to CellLine entries.

When a 1-line scroll landed on the synthetic inter-cell Spacer row
(inserted between non-continuation cells),
`TranscriptScroll::anchor_for` would skip that row and snap back to the
adjacent cell line. That makes the resolved top offset unchanged for
small/coalesced scroll deltas, so scrolling appears to get stuck right
before certain cells (commonly user prompts and command output cells).

Fix this by making spacer rows a first-class scroll anchor:
- Add `TranscriptScroll::ScrolledSpacerBeforeCell` and resolve it back
to the spacer row index when present.
- Update `anchor_for`/`scrolled_by` to preserve spacers instead of
skipping them.
- Treat the new variant as "already anchored" in
`lock_transcript_scroll_to_current_view`.

Tests:
- cargo test -p codex-tui2
2026-01-03 20:26:40 +00:00
leezenn
0c1658d0ec ci: prevent workflows from running on forks (#8629)
## Summary

Forked repositories inherit GitHub Actions workflows including scheduled
ones. This causes:

1. **Wasted Actions minutes** - Scheduled workflows run on forks even
though they will fail
2. **Failed runs** - Workflows requiring `CODEX_OPENAI_API_KEY` fail
immediately on forks
3. **Noise** - Fork owners see failed workflow runs they didn't trigger

This PR adds `if: github.repository == 'openai/codex'` guards to
workflows that should only run on the upstream repository.

### Affected workflows

| Workflow | Trigger | Issue |
|----------|---------|-------|
| `rust-release-prepare` | `schedule: */4 hours` | Runs 6x/day on every
fork |
| `close-stale-contributor-prs` | `schedule: daily` | Runs daily on
every fork |
| `issue-deduplicator` | `issues: opened` | Requires
`CODEX_OPENAI_API_KEY` |
| `issue-labeler` | `issues: opened` | Requires `CODEX_OPENAI_API_KEY` |

### Note

`cla.yml` already has this guard (`github.repository_owner ==
'openai'`), so it was not modified.

## Test plan

- [ ] Verify workflows still run correctly on `openai/codex`
- [ ] Verify workflows are skipped on forks (can check via Actions tab
on any fork)
2026-01-03 13:12:16 -07:00
Josh McKinney
19525efb22 fix: brighten transcript copy affordance (#8697)
- set the pill text to white so it stays legible on light themes
- render the icon in bold light cyan so it stands out
- reran `just fmt`, `just fix -p codex-tui2`, and `cargo test -p
codex-tui2` after the tweak


https://github.com/user-attachments/assets/6c296b8d-84fb-45b9-bce7-57982e0d531b
2026-01-03 11:45:50 -08:00
Josh McKinney
90f37e8549 perf(tui2): cache transcript view rendering (#8693)
The transcript viewport draws every frame. Ratatui's Line::render_ref
does grapheme segmentation and span layout, so repeated redraws can burn
CPU during streaming even when the visible transcript hasn't changed.

Introduce TranscriptViewCache to reduce per-frame work:
- WrappedTranscriptCache memoizes flattened+wrapped transcript lines per
width, appends incrementally as new cells arrive, and rebuilds on width
change, truncation (backtrack), or transcript replacement.
- TranscriptRasterCache caches rasterized rows (Vec<Cell>) per line
index and user-row styling; redraws copy cells instead of rerendering
spans.

The caches are width-scoped and store base transcript content only;
selection highlighting and copy affordances are applied after drawing.
User rows include the row-wide base style in the cached raster.

Refactor transcript_render to expose append_wrapped_transcript_cell for
incremental building and add a test that incremental append matches the
full build.

Add docs/tui2/performance-testing.md as a playbook for macOS sample
profiles and hotspot greps.

Expand transcript_view_cache tests to cover rebuild conditions, raster
equivalence vs direct rendering, user-row caching, and eviction.

Test: cargo test -p codex-tui2
2026-01-03 11:44:27 -08:00
Thibault Sottiaux
ee9d441777 chore: update outdated docs (#8701) 2026-01-03 02:19:52 -08:00
pakrym-oai
1b5095b5d1 Attach more tags to feedback submissions (#8688)
Attach more tags to sentry feedback so it's easier to classify and debug
without having to scan through logs.

Formatting isn't amazing but it's a start.
<img width="1234" height="276" alt="image"
src="https://github.com/user-attachments/assets/521a349d-f627-4051-b511-9811cd5cd933"
/>
2026-01-02 16:51:03 -08:00
dependabot[bot]
c673e7adb6 chore(deps): bump tracing-opentelemetry from 0.31.0 to 0.32.0 in /codex-rs (#8415)
Bumps
[tracing-opentelemetry](https://github.com/tokio-rs/tracing-opentelemetry)
from 0.31.0 to 0.32.0.
<details>
<summary>Release notes</summary>
<p><em>Sourced from <a
href="https://github.com/tokio-rs/tracing-opentelemetry/releases">tracing-opentelemetry's
releases</a>.</em></p>
<blockquote>
<h2>0.32.0</h2>
<h3>Added</h3>
<ul>
<li>Add configuration for including <code>target</code> in spans (<a
href="https://redirect.github.com/tokio-rs/tracing-opentelemetry/pull/222">#222</a>)</li>
</ul>
<h3>Changed</h3>
<ul>
<li>OpenTelemetry context activation (<a
href="https://redirect.github.com/tokio-rs/tracing-opentelemetry/pull/202">#202</a>)
<ul>
<li>Trace ID and span ID can be obtained from <code>OtelData</code> via
dedicated functions. Note that these
will be available only if the context has already been built. (<a
href="https://redirect.github.com/tokio-rs/tracing-opentelemetry/issues/233">#233</a>)</li>
</ul>
</li>
<li>Correctly track entered and exited state for timings (<a
href="https://redirect.github.com/tokio-rs/tracing-opentelemetry/pull/212">#212</a>)</li>
<li>Slightly improve error message on version mismatch (<a
href="https://redirect.github.com/tokio-rs/tracing-opentelemetry/pull/211">#211</a>)</li>
<li>Remove Lazy for thread_local static (<a
href="https://redirect.github.com/tokio-rs/tracing-opentelemetry/pull/215">#215</a>)</li>
<li>Update description of special fields and semantic conventions</li>
</ul>
<h3>Breaking Changes</h3>
<ul>
<li>The attributes <code>code.filepath</code>, <code>code.lineno</code>,
and <code>code.namespace</code> have been renamed to
<code>code.file.path</code>, and <code>code.line.number</code>, and
<code>code.module.name</code>, to align with the opentelemetry
semantic conventions for code. (<a
href="https://redirect.github.com/tokio-rs/tracing-opentelemetry/pull/225">#225</a>)</li>
<li>Upgrade from opentelemetry to 0.31.0. Refer to the upstream
<a
href="https://github.com/open-telemetry/opentelemetry-rust/blob/main/opentelemetry-sdk/CHANGELOG.md#0310">changelog</a>
for more information. (<a
href="https://redirect.github.com/tokio-rs/tracing-opentelemetry/pull/230">#230</a>)</li>
<li>Hold onto <code>MetricsProvider</code> in <code>MetricsLayer</code>
(<a
href="https://redirect.github.com/tokio-rs/tracing-opentelemetry/pull/224">#224</a>)</li>
<li>The attribute <code>otel.status_message</code> was changed to
<code>otel.status_description</code> to align with the
opentelemetry semantic conventions for code. (<a
href="https://redirect.github.com/tokio-rs/tracing-opentelemetry/pull/209">#209</a>)</li>
<li>Remove the <code>metrics_gauge_unstable</code> feature.</li>
</ul>
</blockquote>
</details>
<details>
<summary>Changelog</summary>
<p><em>Sourced from <a
href="https://github.com/tokio-rs/tracing-opentelemetry/blob/v0.1.x/CHANGELOG.md">tracing-opentelemetry's
changelog</a>.</em></p>
<blockquote>
<h2><a
href="https://github.com/tokio-rs/tracing-opentelemetry/compare/v0.31.0...v0.32.0">0.32.0</a>
- 2025-09-29</h2>
<h3>Added</h3>
<ul>
<li>Add configuration for including <code>target</code> in spans (<a
href="https://redirect.github.com/tokio-rs/tracing-opentelemetry/pull/222">#222</a>)</li>
</ul>
<h3>Changed</h3>
<ul>
<li>OpenTelemetry context activation (<a
href="https://redirect.github.com/tokio-rs/tracing-opentelemetry/pull/202">#202</a>)
<ul>
<li>Trace ID and span ID can be obtained from <code>OtelData</code> via
dedicated functions. Note that these
will be available only if the context has already been built. (<a
href="https://redirect.github.com/tokio-rs/tracing-opentelemetry/issues/233">#233</a>)</li>
</ul>
</li>
<li>Correctly track entered and exited state for timings (<a
href="https://redirect.github.com/tokio-rs/tracing-opentelemetry/pull/212">#212</a>)</li>
<li>Slightly improve error message on version mismatch (<a
href="https://redirect.github.com/tokio-rs/tracing-opentelemetry/pull/211">#211</a>)</li>
<li>Remove Lazy for thread_local static (<a
href="https://redirect.github.com/tokio-rs/tracing-opentelemetry/pull/215">#215</a>)</li>
<li>Update description of special fields and semantic conventions</li>
</ul>
<h3>Breaking Changes</h3>
<ul>
<li>The attributes <code>code.filepath</code>, <code>code.lineno</code>,
and <code>code.namespace</code> have been renamed to
<code>code.file.path</code>, and <code>code.line.number</code>, and
<code>code.module.name</code>, to align with the opentelemetry
semantic conventions for code. (<a
href="https://redirect.github.com/tokio-rs/tracing-opentelemetry/pull/225">#225</a>)</li>
<li>Upgrade from opentelemetry to 0.31.0. Refer to the upstream
<a
href="https://github.com/open-telemetry/opentelemetry-rust/blob/main/opentelemetry-sdk/CHANGELOG.md#0310">changelog</a>
for more information. (<a
href="https://redirect.github.com/tokio-rs/tracing-opentelemetry/pull/230">#230</a>)</li>
<li>Hold onto <code>MetricsProvider</code> in <code>MetricsLayer</code>
(<a
href="https://redirect.github.com/tokio-rs/tracing-opentelemetry/pull/224">#224</a>)</li>
<li>The attribute <code>otel.status_message</code> was changed to
<code>otel.status_description</code> to align with the
opentelemetry semantic conventions for code. (<a
href="https://redirect.github.com/tokio-rs/tracing-opentelemetry/pull/209">#209</a>)</li>
<li>Remove the <code>metrics_gauge_unstable</code> feature.</li>
</ul>
</blockquote>
</details>
<details>
<summary>Commits</summary>
<ul>
<li><a
href="f663332dd8"><code>f663332</code></a>
chore: prepare release of 0.32.0</li>
<li><a
href="0154fa470b"><code>0154fa4</code></a>
chore: fix docs link</li>
<li><a
href="d684c2ee36"><code>d684c2e</code></a>
chore: delete removed docs.rs feature</li>
<li><a
href="73a6baf71d"><code>73a6baf</code></a>
feat: make trace ID and span ID public on <code>OtelData</code> (<a
href="https://redirect.github.com/tokio-rs/tracing-opentelemetry/issues/233">#233</a>)</li>
<li><a
href="4ebae2c537"><code>4ebae2c</code></a>
Upgrade to <code>opentelemetry</code> 0.31 (<a
href="https://redirect.github.com/tokio-rs/tracing-opentelemetry/issues/230">#230</a>)</li>
<li><a
href="4fdf56048d"><code>4fdf560</code></a>
fix(layer)!: use otel semantic conventions for code (<a
href="https://redirect.github.com/tokio-rs/tracing-opentelemetry/issues/225">#225</a>)</li>
<li><a
href="612b5b2601"><code>612b5b2</code></a>
chore: fix clippy lints (<a
href="https://redirect.github.com/tokio-rs/tracing-opentelemetry/issues/226">#226</a>)</li>
<li><a
href="c4fe96ac2a"><code>c4fe96a</code></a>
feat: OpenTelemetry context activation (<a
href="https://redirect.github.com/tokio-rs/tracing-opentelemetry/issues/202">#202</a>)</li>
<li><a
href="764cd7365f"><code>764cd73</code></a>
fix(metrics)!: hold onto <code>MetricsProvider</code> in
<code>MetricsLayer</code> (<a
href="https://redirect.github.com/tokio-rs/tracing-opentelemetry/issues/224">#224</a>)</li>
<li><a
href="fd0a58a7f4"><code>fd0a58a</code></a>
feat(layer): add configuration for including <code>target</code> in
spans (<a
href="https://redirect.github.com/tokio-rs/tracing-opentelemetry/issues/222">#222</a>)</li>
<li>Additional commits viewable in <a
href="https://github.com/tokio-rs/tracing-opentelemetry/compare/v0.31.0...v0.32.0">compare
view</a></li>
</ul>
</details>
<br />


[![Dependabot compatibility
score](https://dependabot-badges.githubapp.com/badges/compatibility_score?dependency-name=tracing-opentelemetry&package-manager=cargo&previous-version=0.31.0&new-version=0.32.0)](https://docs.github.com/en/github/managing-security-vulnerabilities/about-dependabot-security-updates#about-compatibility-scores)

Dependabot will resolve any conflicts with this PR as long as you don't
alter it yourself. You can also trigger a rebase manually by commenting
`@dependabot rebase`.

[//]: # (dependabot-automerge-start)
[//]: # (dependabot-automerge-end)

---

<details>
<summary>Dependabot commands and options</summary>
<br />

You can trigger Dependabot actions by commenting on this PR:
- `@dependabot rebase` will rebase this PR
- `@dependabot recreate` will recreate this PR, overwriting any edits
that have been made to it
- `@dependabot merge` will merge this PR after your CI passes on it
- `@dependabot squash and merge` will squash and merge this PR after
your CI passes on it
- `@dependabot cancel merge` will cancel a previously requested merge
and block automerging
- `@dependabot reopen` will reopen this PR if it is closed
- `@dependabot close` will close this PR and stop Dependabot recreating
it. You can achieve the same result by closing it manually
- `@dependabot show <dependency name> ignore conditions` will show all
of the ignore conditions of the specified dependency
- `@dependabot ignore this major version` will close this PR and stop
Dependabot creating any more for this major version (unless you reopen
the PR or upgrade to it yourself)
- `@dependabot ignore this minor version` will close this PR and stop
Dependabot creating any more for this minor version (unless you reopen
the PR or upgrade to it yourself)
- `@dependabot ignore this dependency` will close this PR and stop
Dependabot creating any more for this dependency (unless you reopen the
PR or upgrade to it yourself)


</details>

---------

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: Anton Panasenko <apanasenko@openai.com>
2026-01-02 16:38:16 -08:00
Ahmed Ibrahim
6846bc1115 hard disable remote models refresh when feature is disabled (#8687)
We were refreshing on tag mismatch even with feature is disabled
2026-01-02 15:22:51 -08:00
Ahmed Ibrahim
efd2d76484 Account for last token count on resume (#8677)
last token count in context manager is initialized to 0. Gets populated
only on events from server.

This PR populates it on resume so we can decide if we need to compact or
not.
2026-01-02 23:20:20 +00:00
Eric Traut
82fcc087b5 Fixed broken link in README (#8682) 2026-01-02 15:23:22 -07:00
Josh McKinney
3cfa4bc8be perf(tui2): reduce unnecessary redraws (#8681)
This reduces unnecessary frame scheduling in codex-tui2.

Changes:
- Gate redraw scheduling for streaming deltas when nothing visible
changes.
- Avoid a redraw feedback loop from footer transcript UI state updates.

Why:
- Streaming deltas can arrive at very high frequency; redrawing on every
delta can drive a near-constant render loop.
- BottomPane was requesting another frame after every Draw even when the
derived transcript UI state was unchanged.

Testing:
- cargo test -p codex-tui2

Manual sampling:
- sample "$(pgrep -n codex-tui2)" 3 -file
/tmp/tui2.idle.after.sample.txt
- sample "$(pgrep -n codex-tui2)" 3 -file
/tmp/tui2.streaming.after.sample.txt
2026-01-02 20:15:58 +00:00
Eric Traut
ab753387cc Replaced user documentation with links to developers docs site (#8662)
This eliminates redundant user documentation and allows us to focus our
documentation investments.

I left tombstone files for most of the existing ".md" docs files to
avoid broken links. These now contain brief links to the developers docs
site.
2026-01-02 13:01:53 -07:00
Ahmed Ibrahim
2de731490e Remove model family from tui (#8488)
- Remove model family from tui
2026-01-02 11:30:04 -08:00
pakrym-oai
7078a0b676 Log compaction request bodies (#8676)
We already log request bodies for normal requests, logging for
compaction helps with debugging.
2026-01-02 11:27:37 -08:00
iceweasel-oai
79ce79a62e use a SandboxUsers group for ACLs instead of granting to each sandbox user separately (#8483)
This is more future-proof if we ever decide to add additional Sandbox
Users for new functionality

This also moves some more user-related code into a new file for code
cleanliness
2026-01-02 10:47:26 -08:00
Ahmed Ibrahim
66b7c673e9 Refresh on models etag mismatch (#8491)
- Send models etag
- Refresh models on 412
- This wires `ModelsManager` to `ModelFamily` so we don't mutate it
mid-turn
2026-01-01 11:41:16 -08:00
dependabot[bot]
13c42a077c chore(deps): bump regex-lite from 0.1.7 to 0.1.8 in /codex-rs (#8598)
Bumps [regex-lite](https://github.com/rust-lang/regex) from 0.1.7 to
0.1.8.
<details>
<summary>Changelog</summary>
<p><em>Sourced from <a
href="https://github.com/rust-lang/regex/blob/master/CHANGELOG.md">regex-lite's
changelog</a>.</em></p>
<blockquote>
<h1>0.1.80</h1>
<ul>
<li>[PR <a
href="https://redirect.github.com/rust-lang/regex/issues/292">#292</a>](<a
href="https://redirect.github.com/rust-lang/regex/pull/292">rust-lang/regex#292</a>):
Fixes bug <a
href="https://redirect.github.com/rust-lang/regex/issues/291">#291</a>,
which was introduced by PR <a
href="https://redirect.github.com/rust-lang/regex/issues/290">#290</a>.</li>
</ul>
</blockquote>
</details>
<details>
<summary>Commits</summary>
<ul>
<li><a
href="140f8949da"><code>140f894</code></a>
regex-lite-0.1.8</li>
<li><a
href="27d6d65263"><code>27d6d65</code></a>
1.12.1</li>
<li><a
href="85398ad500"><code>85398ad</code></a>
changelog: 1.12.1</li>
<li><a
href="764efbd305"><code>764efbd</code></a>
api: tweak the lifetime of <code>Captures::get_match</code></li>
<li><a
href="ee6aa55e01"><code>ee6aa55</code></a>
rure-0.2.4</li>
<li><a
href="42076c6bca"><code>42076c6</code></a>
1.12.0</li>
<li><a
href="aef2153e31"><code>aef2153</code></a>
deps: bump to regex-automata 0.4.12</li>
<li><a
href="459dbbeaa9"><code>459dbbe</code></a>
regex-automata-0.4.12</li>
<li><a
href="610bf2d76e"><code>610bf2d</code></a>
regex-syntax-0.8.7</li>
<li><a
href="7dbb384dd0"><code>7dbb384</code></a>
changelog: 1.12.0</li>
<li>Additional commits viewable in <a
href="https://github.com/rust-lang/regex/compare/regex-lite-0.1.7...regex-lite-0.1.8">compare
view</a></li>
</ul>
</details>
<br />


[![Dependabot compatibility
score](https://dependabot-badges.githubapp.com/badges/compatibility_score?dependency-name=regex-lite&package-manager=cargo&previous-version=0.1.7&new-version=0.1.8)](https://docs.github.com/en/github/managing-security-vulnerabilities/about-dependabot-security-updates#about-compatibility-scores)

Dependabot will resolve any conflicts with this PR as long as you don't
alter it yourself. You can also trigger a rebase manually by commenting
`@dependabot rebase`.

[//]: # (dependabot-automerge-start)
[//]: # (dependabot-automerge-end)

---

<details>
<summary>Dependabot commands and options</summary>
<br />

You can trigger Dependabot actions by commenting on this PR:
- `@dependabot rebase` will rebase this PR
- `@dependabot recreate` will recreate this PR, overwriting any edits
that have been made to it
- `@dependabot merge` will merge this PR after your CI passes on it
- `@dependabot squash and merge` will squash and merge this PR after
your CI passes on it
- `@dependabot cancel merge` will cancel a previously requested merge
and block automerging
- `@dependabot reopen` will reopen this PR if it is closed
- `@dependabot close` will close this PR and stop Dependabot recreating
it. You can achieve the same result by closing it manually
- `@dependabot show <dependency name> ignore conditions` will show all
of the ignore conditions of the specified dependency
- `@dependabot ignore this major version` will close this PR and stop
Dependabot creating any more for this major version (unless you reopen
the PR or upgrade to it yourself)
- `@dependabot ignore this minor version` will close this PR and stop
Dependabot creating any more for this minor version (unless you reopen
the PR or upgrade to it yourself)
- `@dependabot ignore this dependency` will close this PR and stop
Dependabot creating any more for this dependency (unless you reopen the
PR or upgrade to it yourself)


</details>

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-12-29 21:08:20 -07:00
dependabot[bot]
a48904de72 chore(deps): bump tokio from 1.47.1 to 1.48.0 in /codex-rs (#8597)
[//]: # (dependabot-start)
⚠️  **Dependabot is rebasing this PR** ⚠️ 

Rebasing might not happen immediately, so don't worry if this takes some
time.

Note: if you make any changes to this PR yourself, they will take
precedence over the rebase.

---

[//]: # (dependabot-end)

Bumps [tokio](https://github.com/tokio-rs/tokio) from 1.47.1 to 1.48.0.
<details>
<summary>Release notes</summary>
<p><em>Sourced from <a
href="https://github.com/tokio-rs/tokio/releases">tokio's
releases</a>.</em></p>
<blockquote>
<h2>Tokio v1.48.0</h2>
<h1>1.48.0 (October 14th, 2025)</h1>
<p>The MSRV is increased to 1.71.</p>
<h3>Added</h3>
<ul>
<li>fs: add <code>File::max_buf_size</code> (<a
href="https://redirect.github.com/tokio-rs/tokio/issues/7594">#7594</a>)</li>
<li>io: export <code>Chain</code> of <code>AsyncReadExt::chain</code>
(<a
href="https://redirect.github.com/tokio-rs/tokio/issues/7599">#7599</a>)</li>
<li>net: add <code>SocketAddr::as_abstract_name</code> (<a
href="https://redirect.github.com/tokio-rs/tokio/issues/7491">#7491</a>)</li>
<li>net: add <code>TcpStream::quickack</code> and
<code>TcpStream::set_quickack</code> (<a
href="https://redirect.github.com/tokio-rs/tokio/issues/7490">#7490</a>)</li>
<li>net: implement <code>AsRef&lt;Self&gt;</code> for
<code>TcpStream</code> and <code>UnixStream</code> (<a
href="https://redirect.github.com/tokio-rs/tokio/issues/7573">#7573</a>)</li>
<li>task: add <code>LocalKey::try_get</code> (<a
href="https://redirect.github.com/tokio-rs/tokio/issues/7666">#7666</a>)</li>
<li>task: implement <code>Ord</code> for <code>task::Id</code> (<a
href="https://redirect.github.com/tokio-rs/tokio/issues/7530">#7530</a>)</li>
</ul>
<h3>Changed</h3>
<ul>
<li>deps: bump windows-sys to version 0.61 (<a
href="https://redirect.github.com/tokio-rs/tokio/issues/7645">#7645</a>)</li>
<li>fs: preserve <code>max_buf_size</code> when cloning a
<code>File</code> (<a
href="https://redirect.github.com/tokio-rs/tokio/issues/7593">#7593</a>)</li>
<li>macros: suppress <code>clippy::unwrap_in_result</code> in
<code>#[tokio::main]</code> (<a
href="https://redirect.github.com/tokio-rs/tokio/issues/7651">#7651</a>)</li>
<li>net: remove <code>PollEvented</code> noise from Debug formats (<a
href="https://redirect.github.com/tokio-rs/tokio/issues/7675">#7675</a>)</li>
<li>process: upgrade <code>Command::spawn_with</code> to use
<code>FnOnce</code> (<a
href="https://redirect.github.com/tokio-rs/tokio/issues/7511">#7511</a>)</li>
<li>sync: remove inner mutex in <code>SetOnce</code> (<a
href="https://redirect.github.com/tokio-rs/tokio/issues/7554">#7554</a>)</li>
<li>sync: use <code>UnsafeCell::get_mut</code> in
<code>Mutex::get_mut</code> and <code>RwLock::get_mut</code> (<a
href="https://redirect.github.com/tokio-rs/tokio/issues/7569">#7569</a>)</li>
<li>time: reduce the generated code size of
<code>Timeout&lt;T&gt;::poll</code> (<a
href="https://redirect.github.com/tokio-rs/tokio/issues/7535">#7535</a>)</li>
</ul>
<h3>Fixed</h3>
<ul>
<li>macros: fix hygiene issue in <code>join!</code> and
<code>try_join!</code> (<a
href="https://redirect.github.com/tokio-rs/tokio/issues/7638">#7638</a>)</li>
<li>net: fix copy/paste errors in udp peek methods (<a
href="https://redirect.github.com/tokio-rs/tokio/issues/7604">#7604</a>)</li>
<li>process: fix error when runtime is shut down on nightly-2025-10-12
(<a
href="https://redirect.github.com/tokio-rs/tokio/issues/7672">#7672</a>)</li>
<li>runtime: use release ordering in <code>wake_by_ref()</code> even if
already woken (<a
href="https://redirect.github.com/tokio-rs/tokio/issues/7622">#7622</a>)</li>
<li>sync: close the <code>broadcast::Sender</code> in
<code>broadcast::Sender::new()</code> (<a
href="https://redirect.github.com/tokio-rs/tokio/issues/7629">#7629</a>)</li>
<li>sync: fix implementation of unused <code>RwLock::try_*</code>
methods (<a
href="https://redirect.github.com/tokio-rs/tokio/issues/7587">#7587</a>)</li>
</ul>
<h3>Unstable</h3>
<ul>
<li>tokio: use cargo features instead of <code>--cfg</code> flags for
<code>taskdump</code> and <code>io_uring</code> (<a
href="https://redirect.github.com/tokio-rs/tokio/issues/7655">#7655</a>,
<a
href="https://redirect.github.com/tokio-rs/tokio/issues/7621">#7621</a>)</li>
<li>fs: support <code>io_uring</code> in <code>fs::write</code> (<a
href="https://redirect.github.com/tokio-rs/tokio/issues/7567">#7567</a>)</li>
<li>fs: support <code>io_uring</code> with <code>File::open()</code> (<a
href="https://redirect.github.com/tokio-rs/tokio/issues/7617">#7617</a>)</li>
<li>fs: support <code>io_uring</code> with <code>OpenOptions</code> (<a
href="https://redirect.github.com/tokio-rs/tokio/issues/7321">#7321</a>)</li>
<li>macros: add <code>local</code> runtime flavor (<a
href="https://redirect.github.com/tokio-rs/tokio/issues/7375">#7375</a>,
<a
href="https://redirect.github.com/tokio-rs/tokio/issues/7597">#7597</a>)</li>
</ul>
<h3>Documented</h3>
<ul>
<li>io: clarify the zero capacity case of
<code>AsyncRead::poll_read</code> (<a
href="https://redirect.github.com/tokio-rs/tokio/issues/7580">#7580</a>)</li>
<li>io: fix typos in the docs of <code>AsyncFd</code> readiness guards
(<a
href="https://redirect.github.com/tokio-rs/tokio/issues/7583">#7583</a>)</li>
<li>net: clarify socket gets closed on drop (<a
href="https://redirect.github.com/tokio-rs/tokio/issues/7526">#7526</a>)</li>
<li>net: clarify the behavior of <code>UCred::pid()</code> on Cygwin (<a
href="https://redirect.github.com/tokio-rs/tokio/issues/7611">#7611</a>)</li>
<li>net: clarify the supported platform of <code>set_reuseport()</code>
and <code>reuseport()</code> (<a
href="https://redirect.github.com/tokio-rs/tokio/issues/7628">#7628</a>)</li>
</ul>
<!-- raw HTML omitted -->
</blockquote>
<p>... (truncated)</p>
</details>
<details>
<summary>Commits</summary>
<ul>
<li><a
href="556820ff84"><code>556820f</code></a>
chore: prepare Tokio v1.48.0 (<a
href="https://redirect.github.com/tokio-rs/tokio/issues/7677">#7677</a>)</li>
<li><a
href="fd1659a052"><code>fd1659a</code></a>
chore: prepare tokio-macros v2.6.0 (<a
href="https://redirect.github.com/tokio-rs/tokio/issues/7676">#7676</a>)</li>
<li><a
href="53e8acac64"><code>53e8aca</code></a>
ci: update nightly version to 2025-10-12 (<a
href="https://redirect.github.com/tokio-rs/tokio/issues/7670">#7670</a>)</li>
<li><a
href="9e5527d1d5"><code>9e5527d</code></a>
process: fix error when runtime is shut down on nightly-2025-10-12 (<a
href="https://redirect.github.com/tokio-rs/tokio/issues/7672">#7672</a>)</li>
<li><a
href="25a24de0e6"><code>25a24de</code></a>
net: remove PollEvented noise from Debug formats (<a
href="https://redirect.github.com/tokio-rs/tokio/issues/7675">#7675</a>)</li>
<li><a
href="c1fa25f300"><code>c1fa25f</code></a>
task: clarify the behavior of several <code>spawn_local</code> methods
(<a
href="https://redirect.github.com/tokio-rs/tokio/issues/7669">#7669</a>)</li>
<li><a
href="e7e02fcf0f"><code>e7e02fc</code></a>
fs: use <code>FileOptions</code> inside <code>fs::File</code> to support
uring (<a
href="https://redirect.github.com/tokio-rs/tokio/issues/7617">#7617</a>)</li>
<li><a
href="f7a7f62959"><code>f7a7f62</code></a>
ci: remove cargo-deny Unicode-DFS-2016 license exception config (<a
href="https://redirect.github.com/tokio-rs/tokio/issues/7619">#7619</a>)</li>
<li><a
href="d1f1499f63"><code>d1f1499</code></a>
tokio: use cargo feature for taskdump support instead of cfg (<a
href="https://redirect.github.com/tokio-rs/tokio/issues/7655">#7655</a>)</li>
<li><a
href="ad6f618952"><code>ad6f618</code></a>
runtime: clarify the behavior of <code>Handle::block_on</code> (<a
href="https://redirect.github.com/tokio-rs/tokio/issues/7665">#7665</a>)</li>
<li>Additional commits viewable in <a
href="https://github.com/tokio-rs/tokio/compare/tokio-1.47.1...tokio-1.48.0">compare
view</a></li>
</ul>
</details>
<br />


[![Dependabot compatibility
score](https://dependabot-badges.githubapp.com/badges/compatibility_score?dependency-name=tokio&package-manager=cargo&previous-version=1.47.1&new-version=1.48.0)](https://docs.github.com/en/github/managing-security-vulnerabilities/about-dependabot-security-updates#about-compatibility-scores)

Dependabot will resolve any conflicts with this PR as long as you don't
alter it yourself. You can also trigger a rebase manually by commenting
`@dependabot rebase`.

[//]: # (dependabot-automerge-start)
[//]: # (dependabot-automerge-end)

---

<details>
<summary>Dependabot commands and options</summary>
<br />

You can trigger Dependabot actions by commenting on this PR:
- `@dependabot rebase` will rebase this PR
- `@dependabot recreate` will recreate this PR, overwriting any edits
that have been made to it
- `@dependabot merge` will merge this PR after your CI passes on it
- `@dependabot squash and merge` will squash and merge this PR after
your CI passes on it
- `@dependabot cancel merge` will cancel a previously requested merge
and block automerging
- `@dependabot reopen` will reopen this PR if it is closed
- `@dependabot close` will close this PR and stop Dependabot recreating
it. You can achieve the same result by closing it manually
- `@dependabot show <dependency name> ignore conditions` will show all
of the ignore conditions of the specified dependency
- `@dependabot ignore this major version` will close this PR and stop
Dependabot creating any more for this major version (unless you reopen
the PR or upgrade to it yourself)
- `@dependabot ignore this minor version` will close this PR and stop
Dependabot creating any more for this minor version (unless you reopen
the PR or upgrade to it yourself)
- `@dependabot ignore this dependency` will close this PR and stop
Dependabot creating any more for this dependency (unless you reopen the
PR or upgrade to it yourself)


</details>

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-12-29 21:07:58 -07:00
dependabot[bot]
4313e0a710 chore(deps): bump tracing-subscriber from 0.3.20 to 0.3.22 in /codex-rs (#8596)
[//]: # (dependabot-start)
⚠️  **Dependabot is rebasing this PR** ⚠️ 

Rebasing might not happen immediately, so don't worry if this takes some
time.

Note: if you make any changes to this PR yourself, they will take
precedence over the rebase.

---

[//]: # (dependabot-end)

Bumps [tracing-subscriber](https://github.com/tokio-rs/tracing) from
0.3.20 to 0.3.22.
<details>
<summary>Release notes</summary>
<p><em>Sourced from <a
href="https://github.com/tokio-rs/tracing/releases">tracing-subscriber's
releases</a>.</em></p>
<blockquote>
<h2>tracing-subscriber 0.3.22</h2>
<h4>Important</h4>
<p>The previous release [0.3.21] was yanked as it depended explicitly on
[tracing-0.1.42], which was yanked due to a breaking change (see <a
href="https://redirect.github.com/tokio-rs/tracing/issues/3424">#3424</a>
for
details). This release contains all the changes from the previous
release, plus
an update to the newer version of <code>tracing</code>.</p>
<h3>Changed</h3>
<ul>
<li><code>tracing</code>: updated to 0.1.43 (<a
href="https://redirect.github.com/tokio-rs/tracing/issues/3427">#3427</a>)</li>
</ul>
<p><a
href="https://redirect.github.com/tokio-rs/tracing/issues/3424">#3424</a>:
<a
href="https://redirect.github.com/tokio-rs/tracing/pull/3424">tokio-rs/tracing#3424</a>
<a
href="https://redirect.github.com/tokio-rs/tracing/issues/3427">#3427</a>:
<a
href="https://redirect.github.com/tokio-rs/tracing/pull/3427">tokio-rs/tracing#3427</a>
[0.3.21]: <a
href="https://github.com/tokio-rs/tracing/releases/tag/tracing-subscriber-0.3.21">https://github.com/tokio-rs/tracing/releases/tag/tracing-subscriber-0.3.21</a>
[tracing-0.1.42]: <a
href="https://github.com/tokio-rs/tracing/releases/tag/tracing-0.1.42">https://github.com/tokio-rs/tracing/releases/tag/tracing-0.1.42</a></p>
<h2>tracing-subscriber 0.3.21</h2>
<h3>Fixed</h3>
<ul>
<li>Change registry exit to decrement local span ref only (<a
href="https://redirect.github.com/tokio-rs/tracing/issues/3331">#3331</a>)</li>
<li>Make Layered propagate <code>on_register_dispatch</code> (<a
href="https://redirect.github.com/tokio-rs/tracing/issues/3379">#3379</a>)</li>
</ul>
<h3>Changed</h3>
<ul>
<li><code>tracing</code>: updated to 0.1.42 (<a
href="https://redirect.github.com/tokio-rs/tracing/issues/3418">#3418</a>)</li>
</ul>
<h3>Performance</h3>
<ul>
<li>Remove <code>clone_span</code> on enter (<a
href="https://redirect.github.com/tokio-rs/tracing/issues/3289">#3289</a>)</li>
</ul>
<h3>Documented</h3>
<ul>
<li>Fix a few small things in the format module (<a
href="https://redirect.github.com/tokio-rs/tracing/issues/3339">#3339</a>)</li>
<li>Fix extra closing brace in layer docs (<a
href="https://redirect.github.com/tokio-rs/tracing/issues/3350">#3350</a>)</li>
<li>Fix link in <code>FmtSpan</code> docs (<a
href="https://redirect.github.com/tokio-rs/tracing/issues/3411">#3411</a>)</li>
</ul>
<p><a
href="https://redirect.github.com/tokio-rs/tracing/issues/3289">#3289</a>:
<a
href="https://redirect.github.com/tokio-rs/tracing/pull/%5B#3289%5D(https://redirect.github.com/tokio-rs/tracing/issues/3289)">tokio-rs/tracing#3289</a>
<a
href="https://redirect.github.com/tokio-rs/tracing/issues/3331">#3331</a>:
<a
href="https://redirect.github.com/tokio-rs/tracing/pull/%5B#3331%5D(https://redirect.github.com/tokio-rs/tracing/issues/3331)">tokio-rs/tracing#3331</a>
<a
href="https://redirect.github.com/tokio-rs/tracing/issues/3339">#3339</a>:
<a
href="https://redirect.github.com/tokio-rs/tracing/pull/%5B#3339%5D(https://redirect.github.com/tokio-rs/tracing/issues/3339)">tokio-rs/tracing#3339</a>
<a
href="https://redirect.github.com/tokio-rs/tracing/issues/3350">#3350</a>:
<a
href="https://redirect.github.com/tokio-rs/tracing/pull/%5B#3350%5D(https://redirect.github.com/tokio-rs/tracing/issues/3350)">tokio-rs/tracing#3350</a>
<a
href="https://redirect.github.com/tokio-rs/tracing/issues/3379">#3379</a>:
<a
href="https://redirect.github.com/tokio-rs/tracing/pull/%5B#3379%5D(https://redirect.github.com/tokio-rs/tracing/issues/3379)">tokio-rs/tracing#3379</a>
<a
href="https://redirect.github.com/tokio-rs/tracing/issues/3411">#3411</a>:
<a
href="https://redirect.github.com/tokio-rs/tracing/pull/%5B#3411%5D(https://redirect.github.com/tokio-rs/tracing/issues/3411)">tokio-rs/tracing#3411</a>
<a
href="https://redirect.github.com/tokio-rs/tracing/issues/3418">#3418</a>:
<a
href="https://redirect.github.com/tokio-rs/tracing/pull/%5B#3418%5D(https://redirect.github.com/tokio-rs/tracing/issues/3418)">tokio-rs/tracing#3418</a></p>
</blockquote>
</details>
<details>
<summary>Commits</summary>
<ul>
<li><a
href="cc44064b3a"><code>cc44064</code></a>
chore: prepare tracing-subscriber 0.3.22 (<a
href="https://redirect.github.com/tokio-rs/tracing/issues/3428">#3428</a>)</li>
<li><a
href="64e1c8d3ae"><code>64e1c8d</code></a>
chore: prepare tracing 0.1.43 (<a
href="https://redirect.github.com/tokio-rs/tracing/issues/3427">#3427</a>)</li>
<li><a
href="7c44f7bb21"><code>7c44f7b</code></a>
tracing: revert &quot;make <code>valueset</code> macro sanitary&quot;
(<a
href="https://redirect.github.com/tokio-rs/tracing/issues/3425">#3425</a>)</li>
<li><a
href="cdaf661c13"><code>cdaf661</code></a>
chore: prepare tracing-mock 0.1.0-beta.2 (<a
href="https://redirect.github.com/tokio-rs/tracing/issues/3422">#3422</a>)</li>
<li><a
href="a164fd3021"><code>a164fd3</code></a>
chore: prepare tracing-journald 0.3.2 (<a
href="https://redirect.github.com/tokio-rs/tracing/issues/3421">#3421</a>)</li>
<li><a
href="405397b8cc"><code>405397b</code></a>
chore: prepare tracing-appender 0.2.4 (<a
href="https://redirect.github.com/tokio-rs/tracing/issues/3420">#3420</a>)</li>
<li><a
href="a9eeed7394"><code>a9eeed7</code></a>
chore: prepare tracing-subscriber 0.3.21 (<a
href="https://redirect.github.com/tokio-rs/tracing/issues/3419">#3419</a>)</li>
<li><a
href="5bd5505478"><code>5bd5505</code></a>
chore: prepare tracing 0.1.42 (<a
href="https://redirect.github.com/tokio-rs/tracing/issues/3418">#3418</a>)</li>
<li><a
href="55086231ec"><code>5508623</code></a>
chore: prepare tracing-attributes 0.1.31 (<a
href="https://redirect.github.com/tokio-rs/tracing/issues/3417">#3417</a>)</li>
<li><a
href="d92b4c0feb"><code>d92b4c0</code></a>
chore: prepare tracing-core 0.1.35 (<a
href="https://redirect.github.com/tokio-rs/tracing/issues/3414">#3414</a>)</li>
<li>Additional commits viewable in <a
href="https://github.com/tokio-rs/tracing/compare/tracing-subscriber-0.3.20...tracing-subscriber-0.3.22">compare
view</a></li>
</ul>
</details>
<br />


[![Dependabot compatibility
score](https://dependabot-badges.githubapp.com/badges/compatibility_score?dependency-name=tracing-subscriber&package-manager=cargo&previous-version=0.3.20&new-version=0.3.22)](https://docs.github.com/en/github/managing-security-vulnerabilities/about-dependabot-security-updates#about-compatibility-scores)

Dependabot will resolve any conflicts with this PR as long as you don't
alter it yourself. You can also trigger a rebase manually by commenting
`@dependabot rebase`.

[//]: # (dependabot-automerge-start)
[//]: # (dependabot-automerge-end)

---

<details>
<summary>Dependabot commands and options</summary>
<br />

You can trigger Dependabot actions by commenting on this PR:
- `@dependabot rebase` will rebase this PR
- `@dependabot recreate` will recreate this PR, overwriting any edits
that have been made to it
- `@dependabot merge` will merge this PR after your CI passes on it
- `@dependabot squash and merge` will squash and merge this PR after
your CI passes on it
- `@dependabot cancel merge` will cancel a previously requested merge
and block automerging
- `@dependabot reopen` will reopen this PR if it is closed
- `@dependabot close` will close this PR and stop Dependabot recreating
it. You can achieve the same result by closing it manually
- `@dependabot show <dependency name> ignore conditions` will show all
of the ignore conditions of the specified dependency
- `@dependabot ignore this major version` will close this PR and stop
Dependabot creating any more for this major version (unless you reopen
the PR or upgrade to it yourself)
- `@dependabot ignore this minor version` will close this PR and stop
Dependabot creating any more for this minor version (unless you reopen
the PR or upgrade to it yourself)
- `@dependabot ignore this dependency` will close this PR and stop
Dependabot creating any more for this dependency (unless you reopen the
PR or upgrade to it yourself)


</details>

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-12-29 21:07:38 -07:00
dependabot[bot]
ce3ff29932 chore(deps): bump toml_edit from 0.23.7 to 0.24.0+spec-1.1.0 in /codex-rs (#8595)
Bumps [toml_edit](https://github.com/toml-rs/toml) from 0.23.7 to
0.24.0+spec-1.1.0.
<details>
<summary>Commits</summary>
<ul>
<li><a
href="2e09401567"><code>2e09401</code></a>
chore: Release</li>
<li><a
href="e32c7a2f9b"><code>e32c7a2</code></a>
chore: Release</li>
<li><a
href="df1c3286de"><code>df1c328</code></a>
docs: Update changelog</li>
<li><a
href="b826cf4914"><code>b826cf4</code></a>
feat(edit)!: Allow <code>set_position(None)</code> (<a
href="https://redirect.github.com/toml-rs/toml/issues/1080">#1080</a>)</li>
<li><a
href="8043f20af7"><code>8043f20</code></a>
feat(edit)!: Allow <code>set_position(None)</code></li>
<li><a
href="a02c0db59f"><code>a02c0db</code></a>
feat: Support TOML 1.1 (<a
href="https://redirect.github.com/toml-rs/toml/issues/1079">#1079</a>)</li>
<li><a
href="5cfb838b15"><code>5cfb838</code></a>
feat(edit): Support TOML 1.1</li>
<li><a
href="1eb4d606d3"><code>1eb4d60</code></a>
feat(toml): Support TOML 1.1</li>
<li><a
href="695d7883d8"><code>695d788</code></a>
feat(edit)!: Multi-line inline tables with trailing commas</li>
<li><a
href="cc4f7acd94"><code>cc4f7ac</code></a>
feat(toml): Multi-line inline tables with trailing commas</li>
<li>Additional commits viewable in <a
href="https://github.com/toml-rs/toml/compare/v0.23.7...v0.24.0">compare
view</a></li>
</ul>
</details>
<br />


[![Dependabot compatibility
score](https://dependabot-badges.githubapp.com/badges/compatibility_score?dependency-name=toml_edit&package-manager=cargo&previous-version=0.23.7&new-version=0.24.0+spec-1.1.0)](https://docs.github.com/en/github/managing-security-vulnerabilities/about-dependabot-security-updates#about-compatibility-scores)

Dependabot will resolve any conflicts with this PR as long as you don't
alter it yourself. You can also trigger a rebase manually by commenting
`@dependabot rebase`.

[//]: # (dependabot-automerge-start)
[//]: # (dependabot-automerge-end)

---

<details>
<summary>Dependabot commands and options</summary>
<br />

You can trigger Dependabot actions by commenting on this PR:
- `@dependabot rebase` will rebase this PR
- `@dependabot recreate` will recreate this PR, overwriting any edits
that have been made to it
- `@dependabot merge` will merge this PR after your CI passes on it
- `@dependabot squash and merge` will squash and merge this PR after
your CI passes on it
- `@dependabot cancel merge` will cancel a previously requested merge
and block automerging
- `@dependabot reopen` will reopen this PR if it is closed
- `@dependabot close` will close this PR and stop Dependabot recreating
it. You can achieve the same result by closing it manually
- `@dependabot show <dependency name> ignore conditions` will show all
of the ignore conditions of the specified dependency
- `@dependabot ignore this major version` will close this PR and stop
Dependabot creating any more for this major version (unless you reopen
the PR or upgrade to it yourself)
- `@dependabot ignore this minor version` will close this PR and stop
Dependabot creating any more for this minor version (unless you reopen
the PR or upgrade to it yourself)
- `@dependabot ignore this dependency` will close this PR and stop
Dependabot creating any more for this dependency (unless you reopen the
PR or upgrade to it yourself)


</details>

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-12-29 21:07:14 -07:00
RosarioYui
810ebe0d2b fix: restrict windows-sys to Windows target (#8522)
I attempted to build codex on LoongArch Linux and encountered
compilation errors.
After investigation, the errors were traced to certain `windows-sys`
features
which rely on platform-specific cfgs that only support x86 and aarch64.

With this change applied, the project now builds and runs successfully
on my
platform:
- OS: AOSC OS (loongarch64)
- Kernel: Linux 6.17
- CPU: Loongson-3A6000

Please let me know if this approach is reasonable, or if there is a
better way
to support additional platforms.
2025-12-24 17:12:04 -07:00
sayan-oai
bf732600ea [chore] add additional_details to StreamErrorEvent + wire through (#8307)
### What

Builds on #8293.

Add `additional_details`, which contains the upstream error message, to
relevant structures used to pass along retryable `StreamError`s.

Uses the new TUI status indicator's `details` field (shows under the
status header) to display the `additional_details` error to the user on
retryable `Reconnecting...` errors. This adds clarity for users for
retryable errors.

Will make corresponding change to VSCode extension to show
`additional_details` as expandable from the `Reconnecting...` cell.

Examples:
<img width="1012" height="326" alt="image"
src="https://github.com/user-attachments/assets/f35e7e6a-8f5e-4a2f-a764-358101776996"
/>

<img width="1526" height="358" alt="image"
src="https://github.com/user-attachments/assets/0029cbc0-f062-4233-8650-cc216c7808f0"
/>
2025-12-24 10:07:38 -08:00
Michael Bolin
38de0a1de4 fix: declare test path relative to $CARGO_MANIFEST_DIR (#8498)
This is another fix to prepare for Buck2. See #8496 for related changes.

---
[//]: # (BEGIN SAPLING FOOTER)
Stack created with [Sapling](https://sapling-scm.com). Best reviewed
with [ReviewStack](https://reviewstack.dev/openai/codex/pull/8498).
* __->__ #8498
* #8496
2025-12-23 19:29:48 -08:00
Michael Bolin
e61bae12e3 feat: introduce codex-utils-cargo-bin as an alternative to assert_cmd::Command (#8496)
This PR introduces a `codex-utils-cargo-bin` utility crate that
wraps/replaces our use of `assert_cmd::Command` and
`escargot::CargoBuild`.

As you can infer from the introduction of `buck_project_root()` in this
PR, I am attempting to make it possible to build Codex under
[Buck2](https://buck2.build) as well as `cargo`. With Buck2, I hope to
achieve faster incremental local builds (largely due to Buck2's
[dice](https://buck2.build/docs/insights_and_knowledge/modern_dice/)
build strategy, as well as benefits from its local build daemon) as well
as faster CI builds if we invest in remote execution and caching.

See
https://buck2.build/docs/getting_started/what_is_buck2/#why-use-buck2-key-advantages
for more details about the performance advantages of Buck2.

Buck2 enforces stronger requirements in terms of build and test
isolation. It discourages assumptions about absolute paths (which is key
to enabling remote execution). Because the `CARGO_BIN_EXE_*` environment
variables that Cargo provides are absolute paths (which
`assert_cmd::Command` reads), this is a problem for Buck2, which is why
we need this `codex-utils-cargo-bin` utility.

My WIP-Buck2 setup sets the `CARGO_BIN_EXE_*` environment variables
passed to a `rust_test()` build rule as relative paths.
`codex-utils-cargo-bin` will resolve these values to absolute paths,
when necessary.


---
[//]: # (BEGIN SAPLING FOOTER)
Stack created with [Sapling](https://sapling-scm.com). Best reviewed
with [ReviewStack](https://reviewstack.dev/openai/codex/pull/8496).
* #8498
* __->__ #8496
2025-12-23 19:29:32 -08:00
Josh McKinney
96a65ff0ed perf(tui): cap redraw scheduling to 60fps (#8499)
Clamp frame draw notifications in the `FrameRequester` scheduler so we
don't redraw more frequently than a user can perceive.

This applies to both `codex-tui` and `codex-tui2`, and keeps the
draw/dispatch loops simple by centralizing the rate limiting in a small
helper module.

- Add `FrameRateLimiter` (pure, unit-tested) to clamp draw deadlines
- Apply the limiter in the scheduler before emitting `TuiEvent::Draw`
- Use immediate redraw requests for scroll paths (scheduler now
coalesces + clamps)
- Add scheduler tests covering immediate/delayed interactions
2025-12-23 19:10:15 -08:00
Ahmed Ibrahim
40de81e7af Remove reasoning format (#8484)
This isn't very useful parameter. 

logic:
```
if model puts `**` in their reasoning, trim it and visualize the header.
if couldn't trim: don't render
if model doesn't support: don't render
```

We can simplify to:
```
if could trim, visualize header.
if not, don't render
```
2025-12-23 16:01:46 -08:00
Michael Bolin
972b5853a0 fix: fix test that was writing temp file to cwd instead of TMPDIR (#8493)
I am trying to support building with [Buck2](https://buck2.build), which
reports which files have changed between invocations of `buck2 test` and
`tmp_delete_example.txt` came up. This turned out to be the reason.
2025-12-23 15:48:06 -08:00
Shijie Rao
fb24c47bea fix: limit output size for exec command in unified exec (#8460)
### Issue
[Investigation
thread](https://openai.slack.com/archives/C095U48JNL9/p1766426234975789)

Github issue: [issue one](https://github.com/openai/codex/issues/8197),
[issue two](https://github.com/openai/codex/issues/8358), [issue
three](https://github.com/openai/codex/issues/7585)
Commonality: working in monorepo and large projects. Multiple threads
going and showing sluggishness ending with a crash and grey background.
Potential high usage of context.
How to reproduce:
* Open the whole monorepo with cursor or VSC on the latest extension.
* Run ls -R in current CWD.

### Change
In unified exec, we do not have max output check to the delta we ouput
for shell commands. This causes issue with our VSCE UI which take the
delta and construct the shell output.
2025-12-23 14:36:15 -08:00
Josh McKinney
f2b740c95d fix(codex-cli): improve ripgrep download diagnostics (#8486)
When rg download fails during npm package staging, log the
target/platform/url and preserve the original exception as the cause.
Emit GitHub Actions log groups and error annotations so the failure is
easier to spot.

Document why a urlopen timeout is set (the default can hang
indefinitely).

This is to make failures in the specific build step easier to understand
/ work out what's failing rather than having a big wall of text (or at
least having an obvious part of it that helps narrow that wall)

---------

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
2025-12-23 21:18:35 +00:00
Josh McKinney
0130a2fa40 feat(tui2): add multi-click transcript selection (#8471)
Support multi-click transcript selection using transcript/viewport
coordinates
(wrapped visual line index + content column), not terminal buffer
positions.

Gestures:
- double click: select word-ish token under cursor
- triple click: select entire wrapped line
- quad click: select paragraph (contiguous non-empty wrapped lines)
- quint+ click: select the entire history cell (all wrapped lines
belonging to a
  single `HistoryCell`, including blank lines inside the cell)

Selection expansion rebuilds the wrapped transcript view from
`HistoryCell::display_lines(width)` so boundaries match on-screen
wrapping during
scroll/resize/streaming reflow. Click grouping is resilient to minor
drag jitter
(some terminals emit tiny Drag events during clicks) and becomes more
tolerant as
the sequence progresses so quad/quint clicks are practical.

Tests cover expansion (word/line/paragraph/cell), sequence resets
(timing, motion,
line changes, real drags), drag jitter, and behavior on spacer lines
between
history cells (paragraph/cell selection prefers the cell above).
2025-12-23 21:05:06 +00:00
sayan-oai
53eb2e9f27 [tui] add optional details to TUI status header (#8293)
### What

Add optional `details` field to TUI's status indicator header. `details`
is shown under the header with text wrapping and a max height of 3
lines.

Duplicated changes to `tui2`.

### Why

Groundwork for displaying error details under `Reconnecting...` for
clarity with retryable errors.

Basic examples
<img width="1012" height="326" alt="image"
src="https://github.com/user-attachments/assets/dd751ceb-b179-4fb2-8fd1-e4784d6366fb"
/>

<img width="1526" height="358" alt="image"
src="https://github.com/user-attachments/assets/bbe466fc-faff-4a78-af7f-3073ccdd8e34"
/>

Truncation example
<img width="936" height="189" alt="image"
src="https://github.com/user-attachments/assets/f3f1b5dd-9050-438b-bb07-bd833c03e889"
/>

### Tests
Tested locally, added tests for truncation.
2025-12-23 12:40:40 -08:00
Josh McKinney
2828549323 fix(tui2): start transcript selection on drag (#8466)
Avoid distracting 1-cell highlights on simple click by tracking an
anchor on mouse down and only creating a visible selection once the
mouse is dragged (selection head set).

When dragging while following the bottom during streaming, request a
scroll lock so the viewport stops moving under the active selection.

Move selection state transitions into transcript_selection helpers
(returning change/lock outcomes for the caller) and add unit tests for
the state machine.
2025-12-22 21:15:57 -08:00
Anton Panasenko
cbc5fb9acf chore: save more about turn context in rollout log file (#8458)
### Motivation
- Persist richer per-turn configuration in rollouts so resumed/forked
sessions and tooling can reason about the exact instruction inputs and
output constraints used for a turn.

### Description
- Extend `TurnContextItem` to include optional `base_instructions`,
`user_instructions`, and `developer_instructions`.
- Record the optional `final_output_json_schema` associated with a turn.
- Add an optional `truncation_policy` to `TurnContextItem` and populate
it when writing turn-context rollout items.
- Introduce a protocol-level `TruncationPolicy` representation and
convert from core truncation policy when recording.

### Testing
- `cargo test -p codex-protocol` (pass)
2025-12-22 19:51:07 -08:00
Josh McKinney
310f2114ae fix(tui2): fix screen corruption (#8463)
Summary

Fixes intermittent screen corruption in tui2 (random stale characters)
by
addressing two terminal state desyncs: nested alt-screen transitions and
the
first-draw viewport clear.

- Make alt-screen enter/leave re-entrant via a small nesting guard so
closing
- Ensure the first viewport draw clears after the viewport is sized,
preventing
old terminal contents from leaking through when diff-based rendering
skips
  space cells.
- Add docs + a small unit test for the alt-screen nesting behavior.

Testing

- cargo test -p codex-tui2
- cargo clippy -p codex-tui2 --all-features --tests
- Manual:
- Opened the transcript overlay and dismissed it repeatedly; verified
the
      normal view redraws cleanly with no leftover characters.
- Ran tui2 in a new folder with no trust settings (and also cleared the
trust setting from config to re-trigger the prompt); verified the
initial
      trust/onboarding screen renders without artifacts.
2025-12-22 19:15:23 -08:00
Michael Bolin
e27d9bd88f feat: honor /etc/codex/config.toml (#8461)
This adds logic to load `/etc/codex/config.toml` and associate it with
`ConfigLayerSource::System` on UNIX. I refactored the code so it shares
logic with the creation of the `ConfigLayerSource::User` layer.
2025-12-22 19:06:04 -08:00
Josh McKinney
414fbe0da9 feat(tui2): add copy selection shortcut + UI affordance (#8462)
- Detect Ctrl+Shift+C vs VS Code Ctrl+Y and surface in footer hints
- Render clickable “⧉ copy” pill near transcript selection (hidden while
dragging)
- Handle copy hotkey + click to copy selection
- Document updated copy UX

VSCode:
<img width="1095" height="413" alt="image"
src="https://github.com/user-attachments/assets/84be0c82-4762-4c3e-80a4-c751c078bdaa"
/>

Ghosty:
<img width="505" height="68" alt="image"
src="https://github.com/user-attachments/assets/109cc1a1-f029-4f7e-a141-4c6ed2da7338"
/>
2025-12-22 18:54:58 -08:00
Michael Bolin
277babba79 feat: load ExecPolicyManager from ConfigLayerStack (#8453)
https://github.com/openai/codex/pull/8354 added support for in-repo
`.config/` files, so this PR updates the logic for loading `*.rules`
files to load `*.rules` files from all relevant layers. The main change
to the business logic is `load_exec_policy()` in
`codex-rs/core/src/exec_policy.rs`.

Note this adds a `config_folder()` method to `ConfigLayerSource` that
returns `Option<AbsolutePathBuf>` so that it is straightforward to
iterate over the sources and get the associated config folder, if any.
2025-12-22 17:24:17 -08:00
Michael Bolin
14dbd0610a chore: include User layer in ConfigLayerStack even if config.toml is empty (#8456)
This is necessary so that `$CODEX_HOME/skills` and `$CODEX_HOME/rules`
still get loaded even if `$CODEX_HOME/config.toml` does not exist. See
#8453.

For now, it is possible to omit this layer when creating a dummy
`ConfigLayerStack` in a test. We can revisit that later, if it turns out
to be the right thing to do.
2025-12-22 16:40:26 -08:00
Josh McKinney
f6275a5142 chore(tui): include tracing targets in file logs (#8418)
with_target(true) is the default for tracing-subscriber, but we
previously disabled it for file output.

Keep it enabled so we can selectively enable specific targets/events at
runtime via RUST_LOG=..., and then grep by target/module in the log file
during troubleshooting.

before and after:

<img width="629" height="194" alt="image"
src="https://github.com/user-attachments/assets/33f7df3f-0c5d-4d3f-b7b7-80b03d4acd21"
/>
2025-12-22 15:59:30 -08:00
Josh McKinney
7d0c5c7bd5 fix(tui2): copy transcript selection outside viewport (#8449)
Copy now operates on the full logical selection range (anchor..head),
not just the visible viewport, so selections that include offscreen
lines copy the expected text.

Selection extraction is factored into `transcript_selection` to make the
logic easier to test and reason about. It reconstructs the wrapped
visual transcript, renders each wrapped line into a 1-row offscreen
Buffer, and reads the selected cells. This keeps clipboard text aligned
with what is rendered (gutter, indentation, wrapping).

Additional behavior:
- Skip continuation cells for wide glyphs (e.g. CJK) so copied text does
not include spurious spaces like "コ X".
- Avoid copying right-margin padding spaces.

Manual tested performed:
- "tell me a story" a few times
- scroll up, select text, scroll down, copy text
- confirm copied text is what you expect
2025-12-22 15:24:52 -08:00
sayan-oai
4673090f73 feat: open prompt in configured external editor (#7606)
Add `ctrl+g` shortcut to enable opening current prompt in configured
editor (`$VISUAL` or `$EDITOR`).


- Prompt is updated with editor's content upon editor close.
- Paste placeholders are automatically expanded when opening the
external editor, and are not "recompressed" on close
- They could be preserved in the editor, but it would be hard to prevent
the user from modifying the placeholder text directly, which would drop
the mapping to the `pending_paste` value
- Image placeholders stay as-is
- `ctrl+g` explanation added to shortcuts menu, snapshot tests updated



https://github.com/user-attachments/assets/4ee05c81-fa49-4e99-8b07-fc9eef0bbfce
2025-12-22 15:12:23 -08:00
Michael Bolin
8e900c210c chore: add ConfigLayerStack as a field of Config (#8448)
This should make it easier to load `.codex/skills/` and `.codex/rules/`
properly throughout.
2025-12-22 14:16:33 -08:00
Ahmed Ibrahim
6b2ef216f1 remove minimal client version (#8447)
This isn't needed value by client
2025-12-22 12:52:24 -08:00
iceweasel-oai
d65fe38b2c use a junction for the cwd while read ACLs are being applied (#8444)
The elevated setup synchronously applies read/write ACLs to any
workspace roots.

However, until we apply *read* permission to the full path, powershell
cannot use some roots as a cwd as it needs access to all parts of the
path in order to apply it as the working directory for a command.

The solution is, while the async read-ACL part of setup is running, use
a "junction" that lives in C:\Users\CodexSandbox{Offline|Online} that
points to the cwd.

Once the read ACLs are applied, we stop using the junction.

-----

this PR also removes some dead code and overly-verbose logging, and has
some light refactoring to the ACL-related functions
2025-12-22 12:23:13 -08:00
github-actions[bot]
7809e36a92 Update models.json (#8446)
Automated update of models.json.

Co-authored-by: aibrahim-oai <219906144+aibrahim-oai@users.noreply.github.com>
2025-12-22 12:05:05 -08:00
Ivan Murashko
0237459f71 Handle concatenation nodes in bash command parser for exec policy (#8395)
The bash command parser in exec_policy was failing to parse commands
with concatenated flag-value patterns like `-g"*.py"` (no space between
flag and quoted value). This caused policy rules like
`prefix_rule(pattern=["rg"])` to not match commands such as `rg -n "foo"
-g"*.py"`.

When tree-sitter-bash parses `-g"*.py"`, it creates a "concatenation"
node containing a word (`-g`) and a string (`"*.py"`). The parser
previously rejected any node type not in the ALLOWED_KINDS list, causing
the entire command parsing to fail and fall back to matching against the
wrapped `bash -lc` command instead of the inner command.

This change:
- Adds "concatenation" to ALLOWED_KINDS in
try_parse_word_only_commands_sequence
- Adds handling for concatenation nodes in parse_plain_command_from_node
that recursively extracts and joins word/string/raw_string children
- Adds test cases for concatenated flag patterns with double and single
quotes

Fixes #8394
2025-12-22 19:59:27 +00:00
Michael Bolin
314937fb11 feat: add support for project_root_markers in config.toml (#8359)
- allow configuring `project_root_markers` in `config.toml`
(user/system/MDM) to control project discovery beyond `.git`
- honor the markers after merging pre-project layers; default to
`[".git"]` when unset and skip ancestor walk when set to an empty array
- document the option and add coverage for alternate markers in config
loader tests
2025-12-22 19:45:45 +00:00
Michael Bolin
8ff16a7714 feat: support in-repo .codex/config.toml entries as sources of config info (#8354)
- We now support `.codex/config.toml` in repo (from `cwd` up to the
first `.git` found, if any) as layers in `ConfigLayerStack`. A new
`ConfigLayerSource::Project` variant was added to support this.
- In doing this work, I realized that we were resolving relative paths
in `config.toml` after merging everything into one `toml::Value`, which
is wrong: paths should be relativized with respect to the folder
containing the `config.toml` that was deserialized. This PR introduces a
deserialize/re-serialize strategy to account for this in
`resolve_config_paths()`. (This is why `Serialize` is added to so many
types as part of this PR.)
- Added tests to verify this new behavior.



---
[//]: # (BEGIN SAPLING FOOTER)
Stack created with [Sapling](https://sapling-scm.com). Best reviewed
with [ReviewStack](https://reviewstack.dev/openai/codex/pull/8354).
* #8359
* __->__ #8354
2025-12-22 11:07:36 -08:00
pakrym-oai
96fdbdd434 Add ExecPolicyManager (#8349)
Move exec policy management into services to keep turn context
immutable.
2025-12-22 09:59:32 -08:00
Dylan Hurd
33e1d0844a feat(windows) start powershell in utf-8 mode (#7902)
## Summary
Adds a FeatureFlag to enforce UTF8 encoding in powershell, particularly
Windows Powershell v5. This should help address issues like #7290.

Notably, this PR does not include the ability to parse `apply_patch`
invocations within UTF8 shell commands (calls to the freeform tool
should not be impacted). I am leaving this out of scope for now. We
should address before this feature becomes Stable, but those cases are
not the default behavior at this time so we're okay for experimentation
phase. We should continue cleaning up the `apply_patch::invocation`
logic and then can handle it more cleanly.

## Testing
- [x] Adds additional testing
2025-12-22 09:36:44 -08:00
dependabot[bot]
b24b7884c7 chore(deps): bump openssl-sys from 0.9.109 to 0.9.111 in /codex-rs (#8416)
Bumps [openssl-sys](https://github.com/rust-openssl/rust-openssl) from
0.9.109 to 0.9.111.
<details>
<summary>Release notes</summary>
<p><em>Sourced from <a
href="https://github.com/rust-openssl/rust-openssl/releases">openssl-sys's
releases</a>.</em></p>
<blockquote>
<h2>openssl-sys-v0.9.111</h2>
<h2>What's Changed</h2>
<ul>
<li>Fix a few typos (most of them found with codespell) by <a
href="https://github.com/botovq"><code>@​botovq</code></a> in <a
href="https://redirect.github.com/rust-openssl/rust-openssl/pull/2502">rust-openssl/rust-openssl#2502</a></li>
<li>Use SHA256 test variant instead of SHA1 by <a
href="https://github.com/abbra"><code>@​abbra</code></a> in <a
href="https://redirect.github.com/rust-openssl/rust-openssl/pull/2504">rust-openssl/rust-openssl#2504</a></li>
<li>pin home to an older version on MSRV CI by <a
href="https://github.com/alex"><code>@​alex</code></a> in <a
href="https://redirect.github.com/rust-openssl/rust-openssl/pull/2509">rust-openssl/rust-openssl#2509</a></li>
<li>Implement set_rsa_oaep_label for AWS-LC/BoringSSL by <a
href="https://github.com/goffrie"><code>@​goffrie</code></a> in <a
href="https://redirect.github.com/rust-openssl/rust-openssl/pull/2508">rust-openssl/rust-openssl#2508</a></li>
<li>sys/evp: add EVP_MAC symbols by <a
href="https://github.com/huwcbjones"><code>@​huwcbjones</code></a> in <a
href="https://redirect.github.com/rust-openssl/rust-openssl/pull/2510">rust-openssl/rust-openssl#2510</a></li>
<li>CI: bump LibreSSL 4.x branches to latest releases by <a
href="https://github.com/botovq"><code>@​botovq</code></a> in <a
href="https://redirect.github.com/rust-openssl/rust-openssl/pull/2513">rust-openssl/rust-openssl#2513</a></li>
<li>Fix unsound OCSP find_status handling of optional next_update field
by <a href="https://github.com/alex"><code>@​alex</code></a> in <a
href="https://redirect.github.com/rust-openssl/rust-openssl/pull/2517">rust-openssl/rust-openssl#2517</a></li>
<li>Release openssl v0.10.75 and openssl-sys v0.9.111 by <a
href="https://github.com/alex"><code>@​alex</code></a> in <a
href="https://redirect.github.com/rust-openssl/rust-openssl/pull/2518">rust-openssl/rust-openssl#2518</a></li>
</ul>
<h2>New Contributors</h2>
<ul>
<li><a href="https://github.com/abbra"><code>@​abbra</code></a> made
their first contribution in <a
href="https://redirect.github.com/rust-openssl/rust-openssl/pull/2504">rust-openssl/rust-openssl#2504</a></li>
<li><a href="https://github.com/goffrie"><code>@​goffrie</code></a> made
their first contribution in <a
href="https://redirect.github.com/rust-openssl/rust-openssl/pull/2508">rust-openssl/rust-openssl#2508</a></li>
</ul>
<p><strong>Full Changelog</strong>: <a
href="https://github.com/rust-openssl/rust-openssl/compare/openssl-sys-v0.9.110...openssl-sys-v0.9.111">https://github.com/rust-openssl/rust-openssl/compare/openssl-sys-v0.9.110...openssl-sys-v0.9.111</a></p>
<h2>openssl-sys-v0.9.110</h2>
<h2>What's Changed</h2>
<ul>
<li>[AIX] use /usr to find_openssl_dir by <a
href="https://github.com/daltenty"><code>@​daltenty</code></a> in <a
href="https://redirect.github.com/rust-openssl/rust-openssl/pull/2401">rust-openssl/rust-openssl#2401</a></li>
<li>Improve support for OPENSSL_NO_COMP and OPENSSL_NO_SRTP by <a
href="https://github.com/justsmth"><code>@​justsmth</code></a> in <a
href="https://redirect.github.com/rust-openssl/rust-openssl/pull/2423">rust-openssl/rust-openssl#2423</a></li>
<li>Add aws-lc-fips feature to allow linking the aws-lc-fips-sys crate
by <a href="https://github.com/skmcgrail"><code>@​skmcgrail</code></a>
in <a
href="https://redirect.github.com/rust-openssl/rust-openssl/pull/2424">rust-openssl/rust-openssl#2424</a></li>
<li>variety of fixes for warnings in new rust by <a
href="https://github.com/alex"><code>@​alex</code></a> in <a
href="https://redirect.github.com/rust-openssl/rust-openssl/pull/2427">rust-openssl/rust-openssl#2427</a></li>
<li>Some API adjustments for LibreSSL 4.2.0 by <a
href="https://github.com/botovq"><code>@​botovq</code></a> in <a
href="https://redirect.github.com/rust-openssl/rust-openssl/pull/2426">rust-openssl/rust-openssl#2426</a></li>
<li>Update OpenSSL documentation URLs to new docs.openssl.org domain by
<a href="https://github.com/alex"><code>@​alex</code></a> in <a
href="https://redirect.github.com/rust-openssl/rust-openssl/pull/2430">rust-openssl/rust-openssl#2430</a></li>
<li>pkey_ctx: add ability to generate DSA params &amp; keys by <a
href="https://github.com/huwcbjones"><code>@​huwcbjones</code></a> in <a
href="https://redirect.github.com/rust-openssl/rust-openssl/pull/2432">rust-openssl/rust-openssl#2432</a></li>
<li>Run tests on windows-11-arm by <a
href="https://github.com/saschanaz"><code>@​saschanaz</code></a> in <a
href="https://redirect.github.com/rust-openssl/rust-openssl/pull/2407">rust-openssl/rust-openssl#2407</a></li>
<li>pkey_ctx: add ability to generate EC params &amp; keys by <a
href="https://github.com/huwcbjones"><code>@​huwcbjones</code></a> in <a
href="https://redirect.github.com/rust-openssl/rust-openssl/pull/2434">rust-openssl/rust-openssl#2434</a></li>
<li>pkey_ctx: add ability to generate DH params &amp; keys by <a
href="https://github.com/huwcbjones"><code>@​huwcbjones</code></a> in <a
href="https://redirect.github.com/rust-openssl/rust-openssl/pull/2433">rust-openssl/rust-openssl#2433</a></li>
<li>pkey_ctx: add ability to generate RSA keys by <a
href="https://github.com/huwcbjones"><code>@​huwcbjones</code></a> in <a
href="https://redirect.github.com/rust-openssl/rust-openssl/pull/2431">rust-openssl/rust-openssl#2431</a></li>
<li>expose more verifier flags/errors for libressl by <a
href="https://github.com/botovq"><code>@​botovq</code></a> in <a
href="https://redirect.github.com/rust-openssl/rust-openssl/pull/2441">rust-openssl/rust-openssl#2441</a></li>
<li>sys/evp: set/get params bindings by <a
href="https://github.com/huwcbjones"><code>@​huwcbjones</code></a> in <a
href="https://redirect.github.com/rust-openssl/rust-openssl/pull/2436">rust-openssl/rust-openssl#2436</a></li>
<li>Add support for argon2d and argon2i variants by <a
href="https://github.com/greateggsgreg"><code>@​greateggsgreg</code></a>
in <a
href="https://redirect.github.com/rust-openssl/rust-openssl/pull/2416">rust-openssl/rust-openssl#2416</a></li>
<li>Bump actions/checkout from 4 to 5 by <a
href="https://github.com/dependabot"><code>@​dependabot</code></a>[bot]
in <a
href="https://redirect.github.com/rust-openssl/rust-openssl/pull/2443">rust-openssl/rust-openssl#2443</a></li>
<li>Update bindgen; Update MSRV to 1.70 by <a
href="https://github.com/justsmth"><code>@​justsmth</code></a> in <a
href="https://redirect.github.com/rust-openssl/rust-openssl/pull/2438">rust-openssl/rust-openssl#2438</a></li>
<li>macros: fully qualify imports by <a
href="https://github.com/huwcbjones"><code>@​huwcbjones</code></a> in <a
href="https://redirect.github.com/rust-openssl/rust-openssl/pull/2445">rust-openssl/rust-openssl#2445</a></li>
<li>Disable AES-CFB128 ciphers for BoringSSL by <a
href="https://github.com/alebastr"><code>@​alebastr</code></a> in <a
href="https://redirect.github.com/rust-openssl/rust-openssl/pull/2447">rust-openssl/rust-openssl#2447</a></li>
<li>Fix missing &quot;__off_t&quot; on NetBSD 10 by <a
href="https://github.com/alebastr"><code>@​alebastr</code></a> in <a
href="https://redirect.github.com/rust-openssl/rust-openssl/pull/2448">rust-openssl/rust-openssl#2448</a></li>
<li>ML-KEM/ML-DSA part 1: openssl-sys changes by <a
href="https://github.com/swenson"><code>@​swenson</code></a> in <a
href="https://redirect.github.com/rust-openssl/rust-openssl/pull/2450">rust-openssl/rust-openssl#2450</a></li>
<li>sys: add symbols to construct an EVP_PKEY from a param builder by <a
href="https://github.com/huwcbjones"><code>@​huwcbjones</code></a> in <a
href="https://redirect.github.com/rust-openssl/rust-openssl/pull/2453">rust-openssl/rust-openssl#2453</a></li>
<li>ec-point: add set_affine_coordinates by <a
href="https://github.com/huwcbjones"><code>@​huwcbjones</code></a> in <a
href="https://redirect.github.com/rust-openssl/rust-openssl/pull/2455">rust-openssl/rust-openssl#2455</a></li>
<li>openssl-sys: add more functions to replace non-deprecated ones by <a
href="https://github.com/huwcbjones"><code>@​huwcbjones</code></a> in <a
href="https://redirect.github.com/rust-openssl/rust-openssl/pull/2457">rust-openssl/rust-openssl#2457</a></li>
<li>ML-KEM/ML-DSA part 2: param builder by <a
href="https://github.com/swenson"><code>@​swenson</code></a> in <a
href="https://redirect.github.com/rust-openssl/rust-openssl/pull/2451">rust-openssl/rust-openssl#2451</a></li>
<li>ML-KEM/ML-DSA part 3: param array locate octet string by <a
href="https://github.com/swenson"><code>@​swenson</code></a> in <a
href="https://redirect.github.com/rust-openssl/rust-openssl/pull/2458">rust-openssl/rust-openssl#2458</a></li>
<li>sys: add encoder &amp; decoder symbols by <a
href="https://github.com/huwcbjones"><code>@​huwcbjones</code></a> in <a
href="https://redirect.github.com/rust-openssl/rust-openssl/pull/2454">rust-openssl/rust-openssl#2454</a></li>
<li>Add bindings for SSL_CIPHER_get_protocol_id by <a
href="https://github.com/jedenastka"><code>@​jedenastka</code></a> in <a
href="https://redirect.github.com/rust-openssl/rust-openssl/pull/2462">rust-openssl/rust-openssl#2462</a></li>
<li>sys/evp: add EVP_PKEY_eq and EVP_PKEY_parameters_eq by <a
href="https://github.com/huwcbjones"><code>@​huwcbjones</code></a> in <a
href="https://redirect.github.com/rust-openssl/rust-openssl/pull/2463">rust-openssl/rust-openssl#2463</a></li>
<li>openssl-sys: make it work without deprecated symbols by <a
href="https://github.com/huwcbjones"><code>@​huwcbjones</code></a> in <a
href="https://redirect.github.com/rust-openssl/rust-openssl/pull/2452">rust-openssl/rust-openssl#2452</a></li>
<li>drop old libressl versions by <a
href="https://github.com/botovq"><code>@​botovq</code></a> in <a
href="https://redirect.github.com/rust-openssl/rust-openssl/pull/2473">rust-openssl/rust-openssl#2473</a></li>
<li>Remove support for LibreSSL &lt; 2.8 by <a
href="https://github.com/botovq"><code>@​botovq</code></a> in <a
href="https://redirect.github.com/rust-openssl/rust-openssl/pull/2475">rust-openssl/rust-openssl#2475</a></li>
</ul>
<!-- raw HTML omitted -->
</blockquote>
<p>... (truncated)</p>
</details>
<details>
<summary>Commits</summary>
<ul>
<li><a
href="09b90d036e"><code>09b90d0</code></a>
Merge pull request <a
href="https://redirect.github.com/rust-openssl/rust-openssl/issues/2518">#2518</a>
from alex/bump-for-release</li>
<li><a
href="26533f3027"><code>26533f3</code></a>
Release openssl v0.10.75 and openssl-sys v0.9.111</li>
<li><a
href="395eccaa49"><code>395ecca</code></a>
Merge pull request <a
href="https://redirect.github.com/rust-openssl/rust-openssl/issues/2517">#2517</a>
from alex/claude/fix-ocsp-find-status-011CUqcGFNKeKJ...</li>
<li><a
href="cc2686771e"><code>cc26867</code></a>
Fix unsound OCSP find_status handling of optional next_update field</li>
<li><a
href="95aa8e8642"><code>95aa8e8</code></a>
Merge pull request <a
href="https://redirect.github.com/rust-openssl/rust-openssl/issues/2513">#2513</a>
from botovq/libressl-stable</li>
<li><a
href="e735a321ff"><code>e735a32</code></a>
CI: bump LibreSSL 4.x branches to latest releases</li>
<li><a
href="21ab91de2d"><code>21ab91d</code></a>
Merge pull request <a
href="https://redirect.github.com/rust-openssl/rust-openssl/issues/2510">#2510</a>
from huwcbjones/huw/sys/evp-mac</li>
<li><a
href="d9161dcac1"><code>d9161dc</code></a>
sys/evp: add EVP_MAC symbols</li>
<li><a
href="3fd4bf2c86"><code>3fd4bf2</code></a>
Merge pull request <a
href="https://redirect.github.com/rust-openssl/rust-openssl/issues/2508">#2508</a>
from goffrie/oaep-label</li>
<li><a
href="52022fd472"><code>52022fd</code></a>
Implement set_rsa_oaep_label for AWS-LC/BoringSSL</li>
<li>Additional commits viewable in <a
href="https://github.com/rust-openssl/rust-openssl/compare/openssl-sys-v0.9.109...openssl-sys-v0.9.111">compare
view</a></li>
</ul>
</details>
<br />


[![Dependabot compatibility
score](https://dependabot-badges.githubapp.com/badges/compatibility_score?dependency-name=openssl-sys&package-manager=cargo&previous-version=0.9.109&new-version=0.9.111)](https://docs.github.com/en/github/managing-security-vulnerabilities/about-dependabot-security-updates#about-compatibility-scores)

Dependabot will resolve any conflicts with this PR as long as you don't
alter it yourself. You can also trigger a rebase manually by commenting
`@dependabot rebase`.

[//]: # (dependabot-automerge-start)
[//]: # (dependabot-automerge-end)

---

<details>
<summary>Dependabot commands and options</summary>
<br />

You can trigger Dependabot actions by commenting on this PR:
- `@dependabot rebase` will rebase this PR
- `@dependabot recreate` will recreate this PR, overwriting any edits
that have been made to it
- `@dependabot merge` will merge this PR after your CI passes on it
- `@dependabot squash and merge` will squash and merge this PR after
your CI passes on it
- `@dependabot cancel merge` will cancel a previously requested merge
and block automerging
- `@dependabot reopen` will reopen this PR if it is closed
- `@dependabot close` will close this PR and stop Dependabot recreating
it. You can achieve the same result by closing it manually
- `@dependabot show <dependency name> ignore conditions` will show all
of the ignore conditions of the specified dependency
- `@dependabot ignore this major version` will close this PR and stop
Dependabot creating any more for this major version (unless you reopen
the PR or upgrade to it yourself)
- `@dependabot ignore this minor version` will close this PR and stop
Dependabot creating any more for this minor version (unless you reopen
the PR or upgrade to it yourself)
- `@dependabot ignore this dependency` will close this PR and stop
Dependabot creating any more for this dependency (unless you reopen the
PR or upgrade to it yourself)


</details>

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-12-22 08:40:18 -07:00
dependabot[bot]
8d5ab97f2b chore(deps): bump clap from 4.5.47 to 4.5.53 in /codex-rs (#8414)
Bumps [clap](https://github.com/clap-rs/clap) from 4.5.47 to 4.5.53.
<details>
<summary>Release notes</summary>
<p><em>Sourced from <a
href="https://github.com/clap-rs/clap/releases">clap's
releases</a>.</em></p>
<blockquote>
<h2>v4.5.53</h2>
<h2>[4.5.53] - 2025-11-19</h2>
<h3>Features</h3>
<ul>
<li>Add <code>default_values_if</code>,
<code>default_values_ifs</code></li>
</ul>
<h2>v4.5.52</h2>
<h2>[4.5.52] - 2025-11-17</h2>
<h3>Fixes</h3>
<ul>
<li>Don't panic when <code>args_conflicts_with_subcommands</code>
conflicts with an <code>ArgGroup</code></li>
</ul>
<h2>v4.5.51</h2>
<h2>[4.5.51] - 2025-10-29</h2>
<h3>Fixes</h3>
<ul>
<li><em>(help)</em> Correctly calculate padding for short flags that
take a value</li>
<li><em>(help)</em> Don't panic on short flags using
<code>ArgAction::Count</code></li>
</ul>
<h2>v4.5.50</h2>
<h2>[4.5.50] - 2025-10-20</h2>
<h3>Features</h3>
<ul>
<li>Accept <code>Cow</code> where <code>String</code> and
<code>&amp;str</code> are accepted</li>
</ul>
<h2>v4.5.48</h2>
<h2>[4.5.48] - 2025-09-19</h2>
<h3>Documentation</h3>
<ul>
<li>Add a new CLI Concepts document as another way of framing clap</li>
<li>Expand the <code>typed_derive</code> cookbook entry</li>
</ul>
</blockquote>
</details>
<details>
<summary>Changelog</summary>
<p><em>Sourced from <a
href="https://github.com/clap-rs/clap/blob/master/CHANGELOG.md">clap's
changelog</a>.</em></p>
<blockquote>
<h2>[4.5.53] - 2025-11-19</h2>
<h3>Features</h3>
<ul>
<li>Add <code>default_values_if</code>,
<code>default_values_ifs</code></li>
</ul>
<h2>[4.5.52] - 2025-11-17</h2>
<h3>Fixes</h3>
<ul>
<li>Don't panic when <code>args_conflicts_with_subcommands</code>
conflicts with an <code>ArgGroup</code></li>
</ul>
<h2>[4.5.51] - 2025-10-29</h2>
<h3>Fixes</h3>
<ul>
<li><em>(help)</em> Correctly calculate padding for short flags that
take a value</li>
<li><em>(help)</em> Don't panic on short flags using
<code>ArgAction::Count</code></li>
</ul>
<h2>[4.5.50] - 2025-10-20</h2>
<h3>Features</h3>
<ul>
<li>Accept <code>Cow</code> where <code>String</code> and
<code>&amp;str</code> are accepted</li>
</ul>
<h2>[4.5.49] - 2025-10-13</h2>
<h3>Fixes</h3>
<ul>
<li><em>(help)</em> Correctly wrap when ANSI escape codes are
present</li>
</ul>
<h2>[4.5.48] - 2025-09-19</h2>
<h3>Documentation</h3>
<ul>
<li>Add a new CLI Concepts document as another way of framing clap</li>
<li>Expand the <code>typed_derive</code> cookbook entry</li>
</ul>
</blockquote>
</details>
<details>
<summary>Commits</summary>
<ul>
<li><a
href="3716f9f428"><code>3716f9f</code></a>
chore: Release</li>
<li><a
href="613b69a6b7"><code>613b69a</code></a>
docs: Update changelog</li>
<li><a
href="d117f7acde"><code>d117f7a</code></a>
Merge pull request <a
href="https://redirect.github.com/clap-rs/clap/issues/6028">#6028</a>
from epage/arg</li>
<li><a
href="cb8255d2f3"><code>cb8255d</code></a>
feat(builder): Allow quoted id's for arg macro</li>
<li><a
href="1036060f13"><code>1036060</code></a>
Merge pull request <a
href="https://redirect.github.com/clap-rs/clap/issues/6025">#6025</a>
from AldaronLau/typos-in-faq</li>
<li><a
href="2fcafc0aee"><code>2fcafc0</code></a>
docs: Fix minor grammar issues in FAQ</li>
<li><a
href="a380b65fe9"><code>a380b65</code></a>
Merge pull request <a
href="https://redirect.github.com/clap-rs/clap/issues/6023">#6023</a>
from epage/template</li>
<li><a
href="4d7ab1483c"><code>4d7ab14</code></a>
chore: Update from _rust/main template</li>
<li><a
href="b8a7ea49d9"><code>b8a7ea4</code></a>
chore(deps): Update Rust Stable to v1.87 (<a
href="https://redirect.github.com/clap-rs/clap/issues/18">#18</a>)</li>
<li><a
href="f9842b3b3f"><code>f9842b3</code></a>
chore: Avoid MSRV problems out of the box</li>
<li>Additional commits viewable in <a
href="https://github.com/clap-rs/clap/compare/clap_complete-v4.5.47...clap_complete-v4.5.53">compare
view</a></li>
</ul>
</details>
<br />


[![Dependabot compatibility
score](https://dependabot-badges.githubapp.com/badges/compatibility_score?dependency-name=clap&package-manager=cargo&previous-version=4.5.47&new-version=4.5.53)](https://docs.github.com/en/github/managing-security-vulnerabilities/about-dependabot-security-updates#about-compatibility-scores)

Dependabot will resolve any conflicts with this PR as long as you don't
alter it yourself. You can also trigger a rebase manually by commenting
`@dependabot rebase`.

[//]: # (dependabot-automerge-start)
[//]: # (dependabot-automerge-end)

---

<details>
<summary>Dependabot commands and options</summary>
<br />

You can trigger Dependabot actions by commenting on this PR:
- `@dependabot rebase` will rebase this PR
- `@dependabot recreate` will recreate this PR, overwriting any edits
that have been made to it
- `@dependabot merge` will merge this PR after your CI passes on it
- `@dependabot squash and merge` will squash and merge this PR after
your CI passes on it
- `@dependabot cancel merge` will cancel a previously requested merge
and block automerging
- `@dependabot reopen` will reopen this PR if it is closed
- `@dependabot close` will close this PR and stop Dependabot recreating
it. You can achieve the same result by closing it manually
- `@dependabot show <dependency name> ignore conditions` will show all
of the ignore conditions of the specified dependency
- `@dependabot ignore this major version` will close this PR and stop
Dependabot creating any more for this major version (unless you reopen
the PR or upgrade to it yourself)
- `@dependabot ignore this minor version` will close this PR and stop
Dependabot creating any more for this minor version (unless you reopen
the PR or upgrade to it yourself)
- `@dependabot ignore this dependency` will close this PR and stop
Dependabot creating any more for this dependency (unless you reopen the
PR or upgrade to it yourself)


</details>

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-12-22 08:39:16 -07:00
dependabot[bot]
6c8470953f chore(deps): bump landlock from 0.4.2 to 0.4.4 in /codex-rs (#8413)
Bumps [landlock](https://github.com/landlock-lsm/rust-landlock) from
0.4.2 to 0.4.4.
<details>
<summary>Release notes</summary>
<p><em>Sourced from <a
href="https://github.com/landlock-lsm/rust-landlock/releases">landlock's
releases</a>.</em></p>
<blockquote>
<h2>v0.4.4</h2>
<p>See <a href="https://crates.io/crates/landlock/0.4.4">crate's
metadata</a> and related <a
href="https://docs.rs/landlock/0.4.4/landlock/">documentation</a>.</p>
<h2>What's Changed</h2>
<p>See summary in <a
href="https://github.com/landlock-lsm/rust-landlock/blob/main/CHANGELOG.md#v044">CHANGELOG.md</a></p>
<ul>
<li>Bump MSRV to 1.68 by <a
href="https://github.com/l0kod"><code>@​l0kod</code></a> in <a
href="https://redirect.github.com/landlock-lsm/rust-landlock/pull/112">landlock-lsm/rust-landlock#112</a></li>
<li>Generate 64-bit and 32-bit bindings by <a
href="https://github.com/l0kod"><code>@​l0kod</code></a> in <a
href="https://redirect.github.com/landlock-lsm/rust-landlock/pull/111">landlock-lsm/rust-landlock#111</a></li>
<li>Print hints about Landlock ABI version by <a
href="https://github.com/l0kod"><code>@​l0kod</code></a> in <a
href="https://redirect.github.com/landlock-lsm/rust-landlock/pull/103">landlock-lsm/rust-landlock#103</a></li>
<li>Improve LandlockStatus by <a
href="https://github.com/l0kod"><code>@​l0kod</code></a> in <a
href="https://redirect.github.com/landlock-lsm/rust-landlock/pull/113">landlock-lsm/rust-landlock#113</a></li>
<li>Bump to v0.4.4 by <a
href="https://github.com/l0kod"><code>@​l0kod</code></a> in <a
href="https://redirect.github.com/landlock-lsm/rust-landlock/pull/114">landlock-lsm/rust-landlock#114</a></li>
</ul>
<p><strong>Full Changelog</strong>: <a
href="https://github.com/landlock-lsm/rust-landlock/compare/v0.4.3...v0.4.4">https://github.com/landlock-lsm/rust-landlock/compare/v0.4.3...v0.4.4</a></p>
<h2>v0.4.3</h2>
<p>See <a href="https://crates.io/crates/landlock/0.4.3">crate's
metadata</a> and related <a
href="https://docs.rs/landlock/0.4.3/landlock/">documentation</a>.</p>
<h2>What's Changed</h2>
<p>See summary in <a
href="https://github.com/landlock-lsm/rust-landlock/blob/main/CHANGELOG.md#v043">CHANGELOG.md</a></p>
<ul>
<li>tests: add case for AccessFs::from_file by <a
href="https://github.com/n0toose"><code>@​n0toose</code></a> in <a
href="https://redirect.github.com/landlock-lsm/rust-landlock/pull/92">landlock-lsm/rust-landlock#92</a></li>
<li>docs: add more background to PathBeneath example by <a
href="https://github.com/n0toose"><code>@​n0toose</code></a> in <a
href="https://redirect.github.com/landlock-lsm/rust-landlock/pull/94">landlock-lsm/rust-landlock#94</a></li>
<li>docs: extend CONTRIBUTING.md file by <a
href="https://github.com/n0toose"><code>@​n0toose</code></a> in <a
href="https://redirect.github.com/landlock-lsm/rust-landlock/pull/95">landlock-lsm/rust-landlock#95</a></li>
<li>Implement common traits for public types by <a
href="https://github.com/l0kod"><code>@​l0kod</code></a> in <a
href="https://redirect.github.com/landlock-lsm/rust-landlock/pull/108">landlock-lsm/rust-landlock#108</a></li>
<li>Bump to v0.4.3 by <a
href="https://github.com/l0kod"><code>@​l0kod</code></a> in <a
href="https://redirect.github.com/landlock-lsm/rust-landlock/pull/109">landlock-lsm/rust-landlock#109</a></li>
</ul>
<h2>New Contributors</h2>
<ul>
<li><a href="https://github.com/n0toose"><code>@​n0toose</code></a> made
their first contribution in <a
href="https://redirect.github.com/landlock-lsm/rust-landlock/pull/92">landlock-lsm/rust-landlock#92</a></li>
</ul>
<p><strong>Full Changelog</strong>: <a
href="https://github.com/landlock-lsm/rust-landlock/compare/v0.4.2...v0.4.3">https://github.com/landlock-lsm/rust-landlock/compare/v0.4.2...v0.4.3</a></p>
</blockquote>
</details>
<details>
<summary>Changelog</summary>
<p><em>Sourced from <a
href="https://github.com/landlock-lsm/rust-landlock/blob/main/CHANGELOG.md">landlock's
changelog</a>.</em></p>
<blockquote>
<h2><a
href="https://github.com/landlock-lsm/rust-landlock/releases/tag/v0.4.4">v0.4.4</a></h2>
<h3>New API</h3>
<ul>
<li>Added support for all architectures ([PR <a
href="https://redirect.github.com/landlock-lsm/rust-landlock/issues/111">#111</a>](<a
href="https://redirect.github.com/landlock-lsm/rust-landlock/pull/111">landlock-lsm/rust-landlock#111</a>)).</li>
<li>Added <code>LandlockStatus</code> type to query the running kernel
and display information about available Landlock features ([PR <a
href="https://redirect.github.com/landlock-lsm/rust-landlock/issues/103">#103</a>](<a
href="https://redirect.github.com/landlock-lsm/rust-landlock/pull/103">landlock-lsm/rust-landlock#103</a>)
and [PR <a
href="https://redirect.github.com/landlock-lsm/rust-landlock/issues/113">#113</a>](<a
href="https://redirect.github.com/landlock-lsm/rust-landlock/pull/113">landlock-lsm/rust-landlock#113</a>)).</li>
</ul>
<h3>Dependencies</h3>
<ul>
<li>Bumped MSRV to Rust 1.68 ([PR <a
href="https://redirect.github.com/landlock-lsm/rust-landlock/issues/112">#112</a>](<a
href="https://redirect.github.com/landlock-lsm/rust-landlock/pull/112">landlock-lsm/rust-landlock#112</a>)).</li>
</ul>
<h3>Testing</h3>
<ul>
<li>Extended CI to build and test on i686 architecture ([PR <a
href="https://redirect.github.com/landlock-lsm/rust-landlock/issues/111">#111</a>](<a
href="https://redirect.github.com/landlock-lsm/rust-landlock/pull/111">landlock-lsm/rust-landlock#111</a>)).</li>
</ul>
<h3>Example</h3>
<ul>
<li>Enhanced sandboxer example to print helpful hints about Landlock
status ([PR <a
href="https://redirect.github.com/landlock-lsm/rust-landlock/issues/103">#103</a>](<a
href="https://redirect.github.com/landlock-lsm/rust-landlock/pull/103">landlock-lsm/rust-landlock#103</a>)).</li>
</ul>
<h2><a
href="https://github.com/landlock-lsm/rust-landlock/releases/tag/v0.4.3">v0.4.3</a></h2>
<h3>New API</h3>
<ul>
<li>Implemented common traits (e.g., <code>Debug</code>) for public
types ([PR <a
href="https://redirect.github.com/landlock-lsm/rust-landlock/issues/108">#108</a>](<a
href="https://redirect.github.com/landlock-lsm/rust-landlock/pull/108">landlock-lsm/rust-landlock#108</a>)).</li>
</ul>
<h3>Documentation</h3>
<ul>
<li>Extended <a
href="https://github.com/landlock-lsm/rust-landlock/blob/main/CONTRIBUTING.md">https://github.com/landlock-lsm/rust-landlock/blob/main/CONTRIBUTING.md</a>
documentation with additional testing and development guidelines ([PR <a
href="https://redirect.github.com/landlock-lsm/rust-landlock/issues/95">#95</a>](<a
href="https://redirect.github.com/landlock-lsm/rust-landlock/pull/95">landlock-lsm/rust-landlock#95</a>)).</li>
<li>Added more background information to <a
href="https://landlock.io/rust-landlock/landlock/fn.path_beneath_rules.html"><code>path_beneath_rules()</code></a>
documentation ([PR <a
href="https://redirect.github.com/landlock-lsm/rust-landlock/issues/94">#94</a>](<a
href="https://redirect.github.com/landlock-lsm/rust-landlock/pull/94">landlock-lsm/rust-landlock#94</a>)).</li>
</ul>
<h3>Testing</h3>
<ul>
<li>Added test case for <code>AccessFs::from_file()</code> method ([PR
<a
href="https://redirect.github.com/landlock-lsm/rust-landlock/issues/92">#92</a>](<a
href="https://redirect.github.com/landlock-lsm/rust-landlock/pull/92">landlock-lsm/rust-landlock#92</a>)).</li>
</ul>
</blockquote>
</details>
<details>
<summary>Commits</summary>
<ul>
<li><a
href="89c56e2db0"><code>89c56e2</code></a>
lib: Bump to v0.4.4</li>
<li><a
href="4fbfbbc4c7"><code>4fbfbbc</code></a>
compat: Improve LandlockStatus</li>
<li><a
href="ec5e00b83b"><code>ec5e00b</code></a>
compat,sandboxer: Add LandlockStatus and print hints</li>
<li><a
href="f79da18085"><code>f79da18</code></a>
uapi: Generate bindings for all architectures</li>
<li><a
href="d29be586a5"><code>d29be58</code></a>
cargo: Bump MSRV to 1.68</li>
<li><a
href="229f152d86"><code>229f152</code></a>
lib: Bump to v0.4.3</li>
<li><a
href="19f01a9975"><code>19f01a9</code></a>
compat: Implement common traits for public types</li>
<li><a
href="cb60a12f9b"><code>cb60a12</code></a>
contributing: Extend documentation</li>
<li><a
href="aa4029d129"><code>aa4029d</code></a>
fs: Add more background to path_beneath_rules() doc</li>
<li><a
href="a7c2b3fe37"><code>a7c2b3f</code></a>
fs: Add test case for AccessFs::from_file</li>
<li>See full diff in <a
href="https://github.com/landlock-lsm/rust-landlock/compare/v0.4.2...v0.4.4">compare
view</a></li>
</ul>
</details>
<br />


[![Dependabot compatibility
score](https://dependabot-badges.githubapp.com/badges/compatibility_score?dependency-name=landlock&package-manager=cargo&previous-version=0.4.2&new-version=0.4.4)](https://docs.github.com/en/github/managing-security-vulnerabilities/about-dependabot-security-updates#about-compatibility-scores)

Dependabot will resolve any conflicts with this PR as long as you don't
alter it yourself. You can also trigger a rebase manually by commenting
`@dependabot rebase`.

[//]: # (dependabot-automerge-start)
[//]: # (dependabot-automerge-end)

---

<details>
<summary>Dependabot commands and options</summary>
<br />

You can trigger Dependabot actions by commenting on this PR:
- `@dependabot rebase` will rebase this PR
- `@dependabot recreate` will recreate this PR, overwriting any edits
that have been made to it
- `@dependabot merge` will merge this PR after your CI passes on it
- `@dependabot squash and merge` will squash and merge this PR after
your CI passes on it
- `@dependabot cancel merge` will cancel a previously requested merge
and block automerging
- `@dependabot reopen` will reopen this PR if it is closed
- `@dependabot close` will close this PR and stop Dependabot recreating
it. You can achieve the same result by closing it manually
- `@dependabot show <dependency name> ignore conditions` will show all
of the ignore conditions of the specified dependency
- `@dependabot ignore this major version` will close this PR and stop
Dependabot creating any more for this major version (unless you reopen
the PR or upgrade to it yourself)
- `@dependabot ignore this minor version` will close this PR and stop
Dependabot creating any more for this minor version (unless you reopen
the PR or upgrade to it yourself)
- `@dependabot ignore this dependency` will close this PR and stop
Dependabot creating any more for this dependency (unless you reopen the
PR or upgrade to it yourself)


</details>

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-12-22 08:35:23 -07:00
dependabot[bot]
334dbe51c6 chore(deps): bump test-log from 0.2.18 to 0.2.19 in /codex-rs (#8412)
Bumps [test-log](https://github.com/d-e-s-o/test-log) from 0.2.18 to
0.2.19.
<details>
<summary>Release notes</summary>
<p><em>Sourced from <a
href="https://github.com/d-e-s-o/test-log/releases">test-log's
releases</a>.</em></p>
<blockquote>
<h2>v0.2.19</h2>
<h2>What's Changed</h2>
<ul>
<li>Adjusted <code>tracing</code> output to log to
<code>stderr</code></li>
</ul>
<h2>New Contributors</h2>
<ul>
<li><a href="https://github.com/dbdr"><code>@​dbdr</code></a> made their
first contribution in <a
href="https://redirect.github.com/d-e-s-o/test-log/pull/64">d-e-s-o/test-log#64</a></li>
</ul>
<p><strong>Full Changelog</strong>: <a
href="https://github.com/d-e-s-o/test-log/compare/v0.2.18...v0.2.19">https://github.com/d-e-s-o/test-log/compare/v0.2.18...v0.2.19</a></p>
</blockquote>
</details>
<details>
<summary>Changelog</summary>
<p><em>Sourced from <a
href="https://github.com/d-e-s-o/test-log/blob/main/CHANGELOG.md">test-log's
changelog</a>.</em></p>
<blockquote>
<h2>0.2.19</h2>
<ul>
<li>Adjusted <code>tracing</code> output to log to
<code>stderr</code></li>
</ul>
</blockquote>
</details>
<details>
<summary>Commits</summary>
<ul>
<li><a
href="b4cd4a3ab6"><code>b4cd4a3</code></a>
Bump version to 0.2.19</li>
<li><a
href="bafe834fe7"><code>bafe834</code></a>
Emit tracing output to stderr</li>
<li><a
href="9e7aafbdcc"><code>9e7aafb</code></a>
Bump actions/checkout from 5 to 6</li>
<li><a
href="7fc59759d8"><code>7fc5975</code></a>
Suggest using [dev-dependencies] instead of [dependencies] in
README.md</li>
<li><a
href="25e7c367e6"><code>25e7c36</code></a>
Bump actions/checkout from 4 to 5</li>
<li><a
href="b633926871"><code>b633926</code></a>
Address clippy reported issue</li>
<li><a
href="628feeea5a"><code>628feee</code></a>
Don't specify patch level for dev-dependencies</li>
<li><a
href="d1a217e2e4"><code>d1a217e</code></a>
Update rstest requirement from 0.25.0 to 0.26.1</li>
<li><a
href="a2c6ba206e"><code>a2c6ba2</code></a>
Document private items in documentation CI job</li>
<li>See full diff in <a
href="https://github.com/d-e-s-o/test-log/compare/v0.2.18...v0.2.19">compare
view</a></li>
</ul>
</details>
<br />


[![Dependabot compatibility
score](https://dependabot-badges.githubapp.com/badges/compatibility_score?dependency-name=test-log&package-manager=cargo&previous-version=0.2.18&new-version=0.2.19)](https://docs.github.com/en/github/managing-security-vulnerabilities/about-dependabot-security-updates#about-compatibility-scores)

Dependabot will resolve any conflicts with this PR as long as you don't
alter it yourself. You can also trigger a rebase manually by commenting
`@dependabot rebase`.

[//]: # (dependabot-automerge-start)
[//]: # (dependabot-automerge-end)

---

<details>
<summary>Dependabot commands and options</summary>
<br />

You can trigger Dependabot actions by commenting on this PR:
- `@dependabot rebase` will rebase this PR
- `@dependabot recreate` will recreate this PR, overwriting any edits
that have been made to it
- `@dependabot merge` will merge this PR after your CI passes on it
- `@dependabot squash and merge` will squash and merge this PR after
your CI passes on it
- `@dependabot cancel merge` will cancel a previously requested merge
and block automerging
- `@dependabot reopen` will reopen this PR if it is closed
- `@dependabot close` will close this PR and stop Dependabot recreating
it. You can achieve the same result by closing it manually
- `@dependabot show <dependency name> ignore conditions` will show all
of the ignore conditions of the specified dependency
- `@dependabot ignore this major version` will close this PR and stop
Dependabot creating any more for this major version (unless you reopen
the PR or upgrade to it yourself)
- `@dependabot ignore this minor version` will close this PR and stop
Dependabot creating any more for this minor version (unless you reopen
the PR or upgrade to it yourself)
- `@dependabot ignore this dependency` will close this PR and stop
Dependabot creating any more for this dependency (unless you reopen the
PR or upgrade to it yourself)


</details>

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-12-22 08:34:39 -07:00
dependabot[bot]
5a0b5d1bd1 chore(deps): bump peter-evans/create-pull-request from 7 to 8 (#8410)
Bumps
[peter-evans/create-pull-request](https://github.com/peter-evans/create-pull-request)
from 7 to 8.
<details>
<summary>Release notes</summary>
<p><em>Sourced from <a
href="https://github.com/peter-evans/create-pull-request/releases">peter-evans/create-pull-request's
releases</a>.</em></p>
<blockquote>
<h2>Create Pull Request v8.0.0</h2>
<h2>What's new in v8</h2>
<ul>
<li>Requires <a
href="https://github.com/actions/runner/releases/tag/v2.327.1">Actions
Runner v2.327.1</a> or later if you are using a self-hosted runner for
Node 24 support.</li>
</ul>
<h2>What's Changed</h2>
<ul>
<li>chore: Update checkout action version to v6 by <a
href="https://github.com/yonas"><code>@​yonas</code></a> in <a
href="https://redirect.github.com/peter-evans/create-pull-request/pull/4258">peter-evans/create-pull-request#4258</a></li>
<li>Update actions/checkout references to <a
href="https://github.com/v6"><code>@​v6</code></a> in docs by <a
href="https://github.com/Copilot"><code>@​Copilot</code></a> in <a
href="https://redirect.github.com/peter-evans/create-pull-request/pull/4259">peter-evans/create-pull-request#4259</a></li>
<li>feat: v8 by <a
href="https://github.com/peter-evans"><code>@​peter-evans</code></a> in
<a
href="https://redirect.github.com/peter-evans/create-pull-request/pull/4260">peter-evans/create-pull-request#4260</a></li>
</ul>
<h2>New Contributors</h2>
<ul>
<li><a href="https://github.com/yonas"><code>@​yonas</code></a> made
their first contribution in <a
href="https://redirect.github.com/peter-evans/create-pull-request/pull/4258">peter-evans/create-pull-request#4258</a></li>
<li><a href="https://github.com/Copilot"><code>@​Copilot</code></a> made
their first contribution in <a
href="https://redirect.github.com/peter-evans/create-pull-request/pull/4259">peter-evans/create-pull-request#4259</a></li>
</ul>
<p><strong>Full Changelog</strong>: <a
href="https://github.com/peter-evans/create-pull-request/compare/v7.0.11...v8.0.0">https://github.com/peter-evans/create-pull-request/compare/v7.0.11...v8.0.0</a></p>
<h2>Create Pull Request v7.0.11</h2>
<h2>What's Changed</h2>
<ul>
<li>fix: restrict remote prune to self-hosted runners by <a
href="https://github.com/peter-evans"><code>@​peter-evans</code></a> in
<a
href="https://redirect.github.com/peter-evans/create-pull-request/pull/4250">peter-evans/create-pull-request#4250</a></li>
</ul>
<p><strong>Full Changelog</strong>: <a
href="https://github.com/peter-evans/create-pull-request/compare/v7.0.10...v7.0.11">https://github.com/peter-evans/create-pull-request/compare/v7.0.10...v7.0.11</a></p>
<h2>Create Pull Request v7.0.10</h2>
<p>⚙️ Fixes an issue where updating a pull request failed when targeting
a forked repository with the same owner as its parent.</p>
<h2>What's Changed</h2>
<ul>
<li>build(deps): bump the github-actions group with 2 updates by <a
href="https://github.com/dependabot"><code>@​dependabot</code></a>[bot]
in <a
href="https://redirect.github.com/peter-evans/create-pull-request/pull/4235">peter-evans/create-pull-request#4235</a></li>
<li>build(deps-dev): bump prettier from 3.6.2 to 3.7.3 in the npm group
by <a
href="https://github.com/dependabot"><code>@​dependabot</code></a>[bot]
in <a
href="https://redirect.github.com/peter-evans/create-pull-request/pull/4240">peter-evans/create-pull-request#4240</a></li>
<li>fix: provider list pulls fallback for multi fork same owner by <a
href="https://github.com/peter-evans"><code>@​peter-evans</code></a> in
<a
href="https://redirect.github.com/peter-evans/create-pull-request/pull/4245">peter-evans/create-pull-request#4245</a></li>
</ul>
<h2>New Contributors</h2>
<ul>
<li><a href="https://github.com/obnyis"><code>@​obnyis</code></a> made
their first contribution in <a
href="https://redirect.github.com/peter-evans/create-pull-request/pull/4064">peter-evans/create-pull-request#4064</a></li>
</ul>
<p><strong>Full Changelog</strong>: <a
href="https://github.com/peter-evans/create-pull-request/compare/v7.0.9...v7.0.10">https://github.com/peter-evans/create-pull-request/compare/v7.0.9...v7.0.10</a></p>
<h2>Create Pull Request v7.0.9</h2>
<p>⚙️ Fixes an <a
href="https://redirect.github.com/peter-evans/create-pull-request/issues/4228">incompatibility</a>
with the recently released <code>actions/checkout@v6</code>.</p>
<h2>What's Changed</h2>
<ul>
<li>~70 dependency updates by <a
href="https://github.com/dependabot"><code>@​dependabot</code></a></li>
<li>docs: fix workaround description about <code>ready_for_review</code>
by <a href="https://github.com/ybiquitous"><code>@​ybiquitous</code></a>
in <a
href="https://redirect.github.com/peter-evans/create-pull-request/pull/3939">peter-evans/create-pull-request#3939</a></li>
<li>Docs: <code>add-paths</code> default behavior by <a
href="https://github.com/joeflack4"><code>@​joeflack4</code></a> in <a
href="https://redirect.github.com/peter-evans/create-pull-request/pull/3928">peter-evans/create-pull-request#3928</a></li>
<li>docs: update to create-github-app-token v2 by <a
href="https://github.com/Goooler"><code>@​Goooler</code></a> in <a
href="https://redirect.github.com/peter-evans/create-pull-request/pull/4063">peter-evans/create-pull-request#4063</a></li>
<li>Fix compatibility with actions/checkout@v6 by <a
href="https://github.com/ericsciple"><code>@​ericsciple</code></a> in <a
href="https://redirect.github.com/peter-evans/create-pull-request/pull/4230">peter-evans/create-pull-request#4230</a></li>
</ul>
<h2>New Contributors</h2>
<ul>
<li><a href="https://github.com/joeflack4"><code>@​joeflack4</code></a>
made their first contribution in <a
href="https://redirect.github.com/peter-evans/create-pull-request/pull/3928">peter-evans/create-pull-request#3928</a></li>
<li><a href="https://github.com/Goooler"><code>@​Goooler</code></a> made
their first contribution in <a
href="https://redirect.github.com/peter-evans/create-pull-request/pull/4063">peter-evans/create-pull-request#4063</a></li>
<li><a
href="https://github.com/ericsciple"><code>@​ericsciple</code></a> made
their first contribution in <a
href="https://redirect.github.com/peter-evans/create-pull-request/pull/4230">peter-evans/create-pull-request#4230</a></li>
</ul>
<!-- raw HTML omitted -->
</blockquote>
<p>... (truncated)</p>
</details>
<details>
<summary>Commits</summary>
<ul>
<li><a
href="98357b18bf"><code>98357b1</code></a>
feat: v8 (<a
href="https://redirect.github.com/peter-evans/create-pull-request/issues/4260">#4260</a>)</li>
<li><a
href="41c0e4b789"><code>41c0e4b</code></a>
Update actions/checkout references to <a
href="https://github.com/v6"><code>@​v6</code></a> in docs (<a
href="https://redirect.github.com/peter-evans/create-pull-request/issues/4259">#4259</a>)</li>
<li><a
href="994332de4c"><code>994332d</code></a>
chore: Update checkout action version to v6 (<a
href="https://redirect.github.com/peter-evans/create-pull-request/issues/4258">#4258</a>)</li>
<li>See full diff in <a
href="https://github.com/peter-evans/create-pull-request/compare/v7...v8">compare
view</a></li>
</ul>
</details>
<br />


[![Dependabot compatibility
score](https://dependabot-badges.githubapp.com/badges/compatibility_score?dependency-name=peter-evans/create-pull-request&package-manager=github_actions&previous-version=7&new-version=8)](https://docs.github.com/en/github/managing-security-vulnerabilities/about-dependabot-security-updates#about-compatibility-scores)

Dependabot will resolve any conflicts with this PR as long as you don't
alter it yourself. You can also trigger a rebase manually by commenting
`@dependabot rebase`.

[//]: # (dependabot-automerge-start)
[//]: # (dependabot-automerge-end)

---

<details>
<summary>Dependabot commands and options</summary>
<br />

You can trigger Dependabot actions by commenting on this PR:
- `@dependabot rebase` will rebase this PR
- `@dependabot recreate` will recreate this PR, overwriting any edits
that have been made to it
- `@dependabot merge` will merge this PR after your CI passes on it
- `@dependabot squash and merge` will squash and merge this PR after
your CI passes on it
- `@dependabot cancel merge` will cancel a previously requested merge
and block automerging
- `@dependabot reopen` will reopen this PR if it is closed
- `@dependabot close` will close this PR and stop Dependabot recreating
it. You can achieve the same result by closing it manually
- `@dependabot show <dependency name> ignore conditions` will show all
of the ignore conditions of the specified dependency
- `@dependabot ignore this major version` will close this PR and stop
Dependabot creating any more for this major version (unless you reopen
the PR or upgrade to it yourself)
- `@dependabot ignore this minor version` will close this PR and stop
Dependabot creating any more for this minor version (unless you reopen
the PR or upgrade to it yourself)
- `@dependabot ignore this dependency` will close this PR and stop
Dependabot creating any more for this dependency (unless you reopen the
PR or upgrade to it yourself)


</details>

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-12-22 08:31:58 -07:00
jif-oai
45727b9ed3 chore: drop undo from the docs (#8431) 2025-12-22 15:09:48 +00:00
Robby He
372de6d2c5 docs: add developer_instructions config option and update descriptions (#8376)
Updates the configuration documentation to clarify and improve the
description of the `developer_instructions` and `instructions` fields.

Documentation updates:

* Added a description for the `developer_instructions` field in
`docs/config.md`, clarifying that it provides additional developer
instructions.
* Updated the comments in `docs/example-config.md` to specify that
`developer_instructions` is injected before `AGENTS.md`, and clarified
that the `instructions` field is ignored and that `AGENTS.md` is
preferred.

___

ref #7973 

Thanks to @miraclebakelaser for the message. I have double-confirmed
that developer instructions are always injected before user
instructions. According to the source code
[codex_core::codex::Session::build_initial_context](https://github.com/openai/codex/blob/rust-v0.77.0-alpha.2/codex-rs/core/src/codex.rs#L1279),
we can see the specific order of these instructions.
2025-12-22 07:37:37 -07:00
jif-oai
7a8407bbb6 chore: un-ship undo (#8424) 2025-12-22 09:53:03 +01:00
Josh McKinney
4e6d6cd798 fix(tui2): constrain transcript mouse selection bounds (#8419)
Ignore mouse events outside the transcript region so composer/footer
interactions do not start or mutate transcript selection state.

A left-click outside the transcript also cancels any active selection.
Selection changes schedule a redraw because mouse events don't
inherently trigger a frame.
2025-12-22 00:11:36 -08:00
Josh McKinney
3c353a3aca test(tui2): re-enable ANSI for VT100 tests (#8423)
Codex Unified Exec injects NO_COLOR=1 (and TERM=dumb) into shell tool
commands to keep output stable. Crossterm respects NO_COLOR and
suppresses ANSI escapes, which breaks our VT100-backed tests that assert
on parsed ANSI color output (they see vt100::Color::Default everywhere).

Force ANSI color output back on in the VT100 test backend by overriding
crossterm's memoized NO_COLOR setting in VT100Backend::new. This keeps
Unified Exec behavior unchanged while making the VT100 tests meaningful
and deterministic under Codex.

> [!WARNING]  
> it's possible that this might be a race condition problem for this and
need to be solved a different way. Feel free to revert if it causes the
opposite problem for other tests that assume NOCOLOR is set. If it does
then we need to probably add some extra AGENTS.md lines for how to run
tests when using unified exec.

(this same change was made in tui, so it's probably safe).
2025-12-22 00:00:32 -08:00
Charlie Weems
99cbba8ea5 Update ghost_commit flag reference to undo (#8091)
Minor documentation update to fix #7966 (documentation of undo flag).
2025-12-21 23:27:54 -08:00
Ahmed Ibrahim
aa83d7da24 fix: do not panic on alphas (#8406)
alphas are used sometimes as stable release
2025-12-22 00:20:53 +00:00
Eric Traut
d281bcfcd4 Point skills docs to developer documentation site (#8407) 2025-12-21 16:18:58 -08:00
Gav Verma
fab1ded484 Remove plan from system skills (#8374)
Removes plan from system skills. It has been rewritten into
`create-plan` for evaluation and feedback:
https://github.com/openai/skills/pull/22
2025-12-20 19:42:53 -08:00
Shijie Rao
987dd7fde3 Chore: remove rmcp feature and exp flag usages (#8087)
### Summary
With codesigning on Mac, Windows and Linux, we should be able to safely
remove `features.rmcp_client` and `use_experimental_use_rmcp_client`
check from the codebase now.
2025-12-20 14:18:00 -08:00
Josh McKinney
63942b883c feat(tui2): tune scrolling inpu based on (#8357)
## TUI2: Normalize Mouse Scroll Input Across Terminals (Wheel +
Trackpad)

This changes TUI2 scrolling to a stream-based model that normalizes
terminal scroll event density into consistent wheel behavior (default:
~3 transcript lines per physical wheel notch) while keeping trackpad
input higher fidelity via fractional accumulation.

Primary code: `codex-rs/tui2/src/tui/scrolling/mouse.rs`

Doc of record (model + probe-derived data):
`codex-rs/tui2/docs/scroll_input_model.md`

### Why

Terminals encode both mouse wheels and trackpads as discrete scroll
up/down events with direction but no magnitude, and they vary widely in
how many raw events they emit per physical wheel notch (commonly 1, 3,
or 9+). Timing alone doesn’t reliably distinguish wheel vs trackpad, so
cadence-based heuristics are unstable across terminals/hardware.

This PR treats scroll input as short *streams* separated by silence or
direction flips, normalizes raw event density into tick-equivalents,
coalesces redraws for dense streams, and exposes explicit config
overrides.

### What Changed

#### Scroll Model (TUI2)

- Stream detection
  - Start a stream on the first scroll event.
  - End a stream on an idle gap (`STREAM_GAP_MS`) or a direction flip.
- Normalization
- Convert raw events into tick-equivalents using per-terminal
`tui.scroll_events_per_tick`.
- Wheel-like vs trackpad-like behavior
- Wheel-like: fixed “classic” lines per wheel notch; flush immediately
for responsiveness.
- Trackpad-like: fractional accumulation + carry across stream
boundaries; coalesce flushes to ~60Hz to avoid floods and reduce “stop
lag / overshoot”.
- Trackpad divisor is intentionally capped: `min(scroll_events_per_tick,
3)` so terminals with dense wheel ticks (e.g. 9 events per notch) don’t
make trackpads feel artificially slow.
- Auto mode (default)
  - Start conservatively as trackpad-like (avoid overshoot).
- Promote to wheel-like if the first tick-worth of events arrives
quickly.
- Fallback for 1-event-per-tick terminals (no tick-completion timing
signal).

#### Trackpad Acceleration

Some terminals produce relatively low vertical event density for
trackpad gestures, which makes large/faster swipes feel sluggish even
when small motions feel correct. To address that, trackpad-like streams
apply a bounded multiplier based on event count:

- `multiplier = clamp(1 + abs(events) / scroll_trackpad_accel_events,
1..scroll_trackpad_accel_max)`

The multiplier is applied to the trackpad stream’s computed line delta
(including carried fractional remainder). Defaults are conservative and
bounded.

#### Config Knobs (TUI2)

All keys live under `[tui]`:

- `scroll_wheel_lines`: lines per physical wheel notch (default: 3).
- `scroll_events_per_tick`: raw vertical scroll events per physical
wheel notch (terminal-specific default; fallback: 3).
- Wheel-like per-event contribution: `scroll_wheel_lines /
scroll_events_per_tick`.
- `scroll_trackpad_lines`: baseline trackpad sensitivity (default: 1).
- Trackpad-like per-event contribution: `scroll_trackpad_lines /
min(scroll_events_per_tick, 3)`.
- `scroll_trackpad_accel_events` / `scroll_trackpad_accel_max`: bounded
trackpad acceleration (defaults: 30 / 3).
- `scroll_mode = auto|wheel|trackpad`: force behavior or use the
heuristic (default: `auto`).
- `scroll_wheel_tick_detect_max_ms`: auto-mode promotion threshold (ms).
- `scroll_wheel_like_max_duration_ms`: auto-mode fallback for
1-event-per-tick terminals (ms).
- `scroll_invert`: invert scroll direction (applies to wheel +
trackpad).

Config docs: `docs/config.md` and field docs in
`codex-rs/core/src/config/types.rs`.

#### App Integration

- The app schedules follow-up ticks to close idle streams (via
`ScrollUpdate::next_tick_in` and `schedule_frame_in`) and finalizes
streams on draw ticks.
  - `codex-rs/tui2/src/app.rs`

#### Docs

- Single doc of record describing the model + preserved probe
findings/spec:
  - `codex-rs/tui2/docs/scroll_input_model.md`

#### Other (jj-only friendliness)

- `codex-rs/tui2/src/diff_render.rs`: prefer stable cwd-relative paths
when the file is under the cwd even if there’s no `.git`.

### Terminal Defaults

Per-terminal defaults are derived from scroll-probe logs (see doc).
Notable:

- Ghostty currently defaults to `scroll_events_per_tick = 3` even though
logs measured ~9 in one setup. This is a deliberate stopgap; if your
Ghostty build emits ~9 events per wheel notch, set:

  ```toml
  [tui]
  scroll_events_per_tick = 9
  ```

### Testing

- `just fmt`
- `just fix -p codex-core --allow-no-vcs`
- `cargo test -p codex-core --lib` (pass)
- `cargo test -p codex-tui2` (scroll tests pass; remaining failures are
known flaky VT100 color tests in `insert_history`)

### Review Focus

- Stream finalization + frame scheduling in `codex-rs/tui2/src/app.rs`.
- Auto-mode promotion thresholds and the 1-event-per-tick fallback
behavior.
- Trackpad divisor cap (`min(events_per_tick, 3)`) and acceleration
defaults.
- Ghostty default tradeoff (3 vs ~9) and whether we should change it.
2025-12-20 12:48:12 -08:00
Michael Bolin
a6974087e5 chore: enusre the logic that creates ConfigLayerStack has access to cwd (#8353)
`load_config_layers_state()` should load config from a
`.codex/config.toml` in any folder between the `cwd` for a thread and
the project root. Though in order to do that,
`load_config_layers_state()` needs to know what the `cwd` is, so this PR
does the work to thread the `cwd` through for existing callsites.

A notable exception is the `/config` endpoint in app server for which a
`cwd` is not guaranteed to be associated with the query, so the `cwd`
param is `Option<AbsolutePathBuf>` to account for this case.

The logic to make use of the `cwd` will be done in a follow-up PR.
2025-12-19 20:11:27 -08:00
Ahmed Ibrahim
f0dc6fd3c7 Rename OpenAI models to models manager (#8346)
# External (non-OpenAI) Pull Request Requirements

Before opening this Pull Request, please read the dedicated
"Contributing" markdown file or your PR may be closed:
https://github.com/openai/codex/blob/main/docs/contributing.md

If your PR conforms to our contribution guidelines, replace this text
with a detailed and high quality description of your changes.

Include a link to a bug report or enhancement request.
2025-12-19 16:20:05 -08:00
sayan-oai
797a68b9f2 bump cargo-deny-action ver (#8345) 2025-12-19 15:23:02 -08:00
Michael Bolin
dc61fc5f50 feat: support allowed_sandbox_modes in requirements.toml (#8298)
This adds support for `allowed_sandbox_modes` in `requirements.toml` and
provides legacy support for constraining sandbox modes in
`managed_config.toml`. This is converted to `Constrained<SandboxPolicy>`
in `ConfigRequirements` and applied to `Config` such that constraints
are enforced throughout the harness.

Note that, because `managed_config.toml` is deprecated, we do not add
support for the new `external-sandbox` variant recently introduced in
https://github.com/openai/codex/pull/8290. As noted, that variant is not
supported in `config.toml` today, but can be configured programmatically
via app server.
2025-12-19 21:09:20 +00:00
RQfreefly
ec3738b47e feat: move file name derivation into codex-file-search (#8334)
## Summary

  - centralize file name derivation in codex-file-search
  - reuse the helper in app-server fuzzy search to avoid duplicate logic
  - add unit tests for file_name_from_path

  ## Testing

  - cargo test -p codex-file-search
  - cargo test -p codex-app-server
2025-12-19 12:50:55 -08:00
Josh McKinney
1d4463ba81 feat(tui2): coalesce transcript scroll redraws (#8295)
Problem
- Mouse wheel events were scheduling a redraw on every event, which
could backlog and create lag during fast scrolling.

Solution
- Schedule transcript scroll redraws with a short delay (16ms) so the
frame requester coalesces bursts into fewer draws.

Why
- Smooths rapid wheel scrolling while keeping the UI responsive.

Testing
- Manual: Scrolled in iTerm and Ghostty; no lag observed.
- `cargo clippy --fix --all-features --tests --allow-dirty
--allow-no-vcs -p codex-tui2`
2025-12-19 12:19:01 -08:00
github-actions[bot]
e3d3445748 Update models.json (#8168)
Automated update of models.json.

Co-authored-by: aibrahim-oai <219906144+aibrahim-oai@users.noreply.github.com>
2025-12-19 12:06:34 -08:00
Michael Bolin
0a7021de72 fix: enable resume_warning that was missing from mod.rs (#8333)
This test was introduced in https://github.com/openai/codex/pull/6507,
but was not included in `mod.rs`. It does not appear that it was getting
compiled?
2025-12-19 19:21:47 +00:00
Michael Bolin
7e5c343ef5 feat: make ConstraintError an enum (#8330)
This will make it easier to test for expected errors in unit tests since
we can compare based on the field values rather than the message (which
might change over time). See https://github.com/openai/codex/pull/8298
for an example.

It also ensures more consistency in the way a `ConstraintError` is
constructed.
2025-12-19 19:03:50 +00:00
GalaxyDetective
014235f533 Fix: /undo destructively interacts with git staging (#8214) (#8303)
Fixes #8214 by removing the '--staged' flag from the undo git restore
command. This ensures that while the working tree is reverted to the
snapshot state, the user's staged changes (index) are preserved,
preventing data loss. Also adds a regression test.
2025-12-19 10:07:41 -08:00
jdijk-deventit
b15b5082c6 Fix link to contributing.md in experimental.md (#8311)
# External (non-OpenAI) Pull Request Requirements

Before opening this Pull Request, please read the dedicated
"Contributing" markdown file or your PR may be closed:
https://github.com/openai/codex/blob/main/docs/contributing.md

If your PR conforms to our contribution guidelines, replace this text
with a detailed and high quality description of your changes.

Include a link to a bug report or enhancement request.
2025-12-19 09:42:56 -08:00
Gav Verma
37071e7e5c Update system skills from OSS repo (#8328)
https://github.com/openai/skills/tree/main/skills/.system
2025-12-19 09:31:04 -08:00
xl-openai
eeda6a5004 Revert "Keep skills feature flag default OFF for windows." (#8325)
Reverts openai/codex#8308
2025-12-19 16:22:14 +00:00
xl-openai
6f94a90797 Keep skills feature flag default OFF for windows. (#8308)
Keep windows OFF first.
2025-12-18 21:57:15 -08:00
xl-openai
339b052d68 Fix admin skills. (#8305)
We were assembling the skill roots in two different places, and the
admin root was missing in one of them. This change centralizes root
selection into a helper so both paths stay in sync.
2025-12-19 04:10:19 +00:00
Gav Verma
f4371d2f6c Add short descriptions to system skills (#8301) 2025-12-19 02:44:53 +00:00
xl-openai
8120c8765b Support admin scope skills. (#8296)
a new scope reads from /etc/codex
2025-12-19 02:28:56 +00:00
xl-openai
d35337227a skills feature default on. (#8297)
skills default on.
2025-12-18 18:26:46 -08:00
xl-openai
ba835c3c36 Fix tests (#8299)
Fix broken tests.
2025-12-19 02:07:23 +00:00
xl-openai
dcc01198e2 UI tweaks on skills popup. (#8250)
Only display the skill name (not the folder), and truncate the skill
description to a maximum of two lines.
2025-12-19 01:16:51 +00:00
jif-oai
6c76d17713 feat: collapse "waiting" of unified_exec (#8257)
Screenshots here but check the snapshot files to see it better
<img width="712" height="408" alt="Screenshot 2025-12-18 at 11 58 02"
src="https://github.com/user-attachments/assets/84a2c410-0767-4870-84d1-ae1c0d4c445e"
/>
<img width="523" height="352" alt="Screenshot 2025-12-18 at 11 17 41"
src="https://github.com/user-attachments/assets/d029c7ea-0feb-4493-9dca-af43a0c70c52"
/>
2025-12-18 17:03:43 -08:00
Anton Panasenko
3429de21b3 feat: introduce ExternalSandbox policy (#8290)
## Description

Introduced `ExternalSandbox` policy to cover use case when sandbox
defined by outside environment, effectively it translates to
`SandboxMode#DangerFullAccess` for file system (since sandbox configured
on container level) and configurable `network_access` (either Restricted
or Enabled by outside environment).

as example you can configure `ExternalSandbox` policy as part of
`sendUserTurn` v1 app_server API:

```
 {
            "conversationId": <id>,
            "cwd": <cwd>,
            "approvalPolicy": "never",
            "sandboxPolicy": {
                  "type": ""external-sandbox",
                  "network_access": "enabled"/"restricted"
            },
            "model": <model>,
            "effort": <effort>,
            ....
        }
```
2025-12-18 17:02:03 -08:00
Michael Bolin
3d4ced3ff5 chore: migrate from Config::load_from_base_config_with_overrides to ConfigBuilder (#8276)
https://github.com/openai/codex/pull/8235 introduced `ConfigBuilder` and
this PR updates all call non-test call sites to use it instead of
`Config::load_from_base_config_with_overrides()`.

This is important because `load_from_base_config_with_overrides()` uses
an empty `ConfigRequirements`, which is a reasonable default for testing
so the tests are not influenced by the settings on the host. This method
is now guarded by `#[cfg(test)]` so it cannot be used by business logic.

Because `ConfigBuilder::build()` is `async`, many of the test methods
had to be migrated to be `async`, as well. On the bright side, this made
it possible to eliminate a bunch of `block_on_future()` stuff.
2025-12-18 16:12:52 -08:00
Koichi Shiraishi
2d9826098e fix: remove duplicate shell_snapshot FeatureSpec (#8274)
regression: #8199

Signed-off-by: Koichi Shiraishi <zchee.io@gmail.com>
2025-12-18 15:55:47 -08:00
Michael Bolin
46baedd7cb fix: change codex/sandbox-state/update from a notification to a request (#8142)
Historically, `accept_elicitation_for_prompt_rule()` was flaky because
we were using a notification to update the sandbox followed by a `shell`
tool request that we expected to be subject to the new sandbox config,
but because [rmcp](https://crates.io/crates/rmcp) MCP servers delegate
each incoming message to a new Tokio task, messages are not guaranteed
to be processed in order, so sometimes the `shell` tool call would run
before the notification was processed.

Prior to this PR, we relied on a generous `sleep()` between the
notification and the request to reduce the change of the test flaking
out.

This PR implements a proper fix, which is to use a _request_ instead of
a notification for the sandbox update so that we can wait for the
response to the sandbox request before sending the request to the
`shell` tool call. Previously, `rmcp` did not support custom requests,
but I fixed that in
https://github.com/modelcontextprotocol/rust-sdk/pull/590, which made it
into the `0.12.0` release (see #8288).

This PR updates `shell-tool-mcp` to expect
`"codex/sandbox-state/update"` as a _request_ instead of a notification
and sends the appropriate ack. Note this behavior is tied to our custom
`codex/sandbox-state` capability, which Codex honors as an MCP client,
which is why `core/src/mcp_connection_manager.rs` had to be updated as
part of this PR, as well.

This PR also updates the docs at `shell-tool-mcp/README.md`.
2025-12-18 15:32:01 -08:00
xl-openai
358a5baba0 Support skills shortDescription. (#8278)
Allow SKILL.md to specify a more human-readable short description as
skill metadata.
2025-12-18 23:13:18 +00:00
Gav Verma
1cd1cf17c6 Update system skills bundled with codex-rs (#8253)
Synced with https://github.com/openai/skills/tree/main/skills/.system
2025-12-18 14:30:00 -08:00
Michael Bolin
53f53173a8 chore: upgrade rmcp crate from 0.10.0 to 0.12.0 (#8288)
Version `0.12.0` includes
https://github.com/modelcontextprotocol/rust-sdk/pull/590, which I will
use in https://github.com/openai/codex/pull/8142.

Changes:

- `rmcp::model::CustomClientNotification` was renamed to
`rmcp::model::CustomNotification`
- a bunch of types have a `meta` field now, but it is `Option`, so I
added `meta: None` to a bunch of things
2025-12-18 14:28:46 -08:00
Andrew Ambrosino
9fb9ed6cea Set exclude to true by default in app server (#8281) 2025-12-18 14:28:30 -08:00
pakrym-oai
8f0b383621 model list (#8286)
<img width="200" alt="7ff2254b-e96f-42fc-8232-b4e76cb26248"
src="https://github.com/user-attachments/assets/1f56799d-e2cd-4b69-9290-854943f7c6b6"
/>
2025-12-18 14:13:49 -08:00
Owen Lin
d7ae342ff4 feat(app-server): add v2 deprecation notice (#8285)
Add a v2 event for deprecation notices so we can get rid of
`codex/event/deprecation_notice`.
2025-12-18 21:45:36 +00:00
Michael Bolin
2f048f2063 feat: add support for /etc/codex/requirements.toml on UNIX (#8277)
This implements the new config design where config _requirements_ are
loaded separately (and with a special schema) as compared to config
_settings_. In particular, on UNIX, with this PR, you could define
`/etc/codex/requirements.toml` with:

```toml
allowed_approval_policies = ["never", "on-request"]
```

to enforce that `Config.approval_policy` must be one of those two values
when Codex runs.

We plan to expand the set of things that can be restricted by
`/etc/codex/requirements.toml` in short order.

Note that requirements can come from several sources:

- new MDM key on macOS (not implemented yet)
- `/etc/codex/requirements.toml`
- re-interpretation of legacy MDM key on macOS
(`com.openai.codex/config_toml_base64`)
- re-interpretation of legacy `/etc/codex/managed_config.toml`

So our resolution strategy is to load TOML data from those sources, in
order. Later TOMLs are "merged" into previous TOMLs, but any field that
is already set cannot be overwritten. See
`ConfigRequirementsToml::merge_unset_fields()`.
2025-12-18 13:36:55 -08:00
jif-oai
4fb0b547d6 feat: add /ps (#8279)
See snapshots for view of edge cases
This is still named `UnifiedExecSessions` for consistency across the
code but should be renamed to `BackgroundTerminals` in a follow-up

Example:
<img width="945" height="687" alt="Screenshot 2025-12-18 at 20 12 53"
src="https://github.com/user-attachments/assets/92f39ff2-243c-4006-b402-e3fa9e93c952"
/>
2025-12-18 21:09:06 +00:00
jif-oai
87abf06e78 fix: flaky tests 5 (#8282) 2025-12-18 21:08:43 +00:00
iceweasel-oai
6395430220 add a default dacl to restricted token to enable reading of pipes (#8280)
this fixes sandbox errors (legacy and elevated) for commands that
include pipes, which the model often favors.
2025-12-18 12:59:52 -08:00
Josh McKinney
df46ea48a2 Terminal Detection Metadata for Per-Terminal Scroll Scaling (#8252)
# Terminal Detection Metadata for Per-Terminal Scroll Scaling

## Summary
Expand terminal detection into structured metadata (`TerminalInfo`) with
multiplexer awareness, plus a testable environment shim and
characterization tests.

## Context / Motivation
- TUI2 owns its viewport and scrolling model (see
`codex-rs/tui2/docs/tui_viewport_and_history.md`), so scroll behavior
must be consistent across terminals and independent of terminal
scrollback quirks.
- Prior investigations show mouse wheel scroll deltas vary noticeably by
terminal. To tune scroll scaling (line increments per wheel tick) we
need reliable terminal identification, including when running inside
tmux/zellij.
- tmux is especially tricky because it can mask the underlying terminal;
we now consult `tmux display-message` client termtype/name to attribute
sessions to the actual terminal rather than tmux itself.
- This remains backwards compatible with the existing OpenTelemetry
user-agent token because `user_agent()` is still derived from the same
environment signals (now via `TerminalInfo`).

## Changes
- Introduce `TerminalInfo`, `TerminalName`, and `Multiplexer` with
`TERM_PROGRAM`/`TERM`/multiplexer detection and user-agent formatting in
`codex-rs/core/src/terminal.rs`.
- Add an injectable `Environment` trait + `FakeEnvironment` for testing,
and comprehensive characterization tests covering known terminals, tmux
client termtype/name, and zellij.
- Document module usage and detection order; update `terminal_info()` to
be the primary interface for callers.

## Testing
- `cargo test -p codex-core terminal::tests`
- manually checked ghostty, iTerm2, Terminal.app, vscode, tmux, zellij,
Warp, alacritty, kitty.
```
2025-12-18T07:07:49.191421Z  INFO Detected terminal info terminal=TerminalInfo { name: Iterm2, term_program: Some("iTerm.app"), version: Some("3.6.6"), term: None, multiplexer: None }
2025-12-18T07:07:57.991776Z  INFO Detected terminal info terminal=TerminalInfo { name: AppleTerminal, term_program: Some("Apple_Terminal"), version: Some("455.1"), term: None, multiplexer: None }
2025-12-18T07:08:07.732095Z  INFO Detected terminal info terminal=TerminalInfo { name: WarpTerminal, term_program: Some("WarpTerminal"), version: Some("v0.2025.12.10.08.12.stable_03"), term: None, multiplexer: None }
2025-12-18T07:08:24.860316Z  INFO Detected terminal info terminal=TerminalInfo { name: Kitty, term_program: None, version: None, term: None, multiplexer: None }
2025-12-18T07:08:38.302761Z  INFO Detected terminal info terminal=TerminalInfo { name: Alacritty, term_program: None, version: None, term: None, multiplexer: None }
2025-12-18T07:08:50.887748Z  INFO Detected terminal info terminal=TerminalInfo { name: VsCode, term_program: Some("vscode"), version: Some("1.107.1"), term: None, multiplexer: None }
2025-12-18T07:10:01.309802Z  INFO Detected terminal info terminal=TerminalInfo { name: WezTerm, term_program: Some("WezTerm"), version: Some("20240203-110809-5046fc22"), term: None, multiplexer: None }
2025-12-18T08:05:17.009271Z  INFO Detected terminal info terminal=TerminalInfo { name: Ghostty, term_program: Some("ghostty"), version: Some("1.2.3"), term: None, multiplexer: None }
2025-12-18T08:05:23.819973Z  INFO Detected terminal info terminal=TerminalInfo { name: Ghostty, term_program: Some("ghostty"), version: Some("1.2.3"), term: Some("xterm-ghostty"), multiplexer: Some(Tmux { version: Some("3.6a") }) }
2025-12-18T08:05:35.572853Z  INFO Detected terminal info terminal=TerminalInfo { name: Ghostty, term_program: Some("ghostty"), version: Some("1.2.3"), term: None, multiplexer: Some(Zellij) }
```

## Notes / Follow-ups
- Next step is to wire `TerminalInfo` into TUI2’s scroll scaling
configuration and add a per-terminal tuning table.
- The log output in TUI2 helps validate real-world detection before
applying behavior changes.
2025-12-18 12:50:00 -08:00
iceweasel-oai
e9023d5662 use mainline version as baseline in ci (#8271) 2025-12-18 11:53:36 -08:00
iceweasel-oai
ad41182ee8 grant read ACL to exe directory first so we can call the command runner (#8275)
when granting read access to the sandbox user, grant the
codex/command-runner exe directory first so commands can run before the
entire read ACL process is finished.
2025-12-18 19:52:32 +00:00
Celia Chen
2e5d52cb14 [release] Add a dmg target for MacOS (#8207)
Add a dmg target that bundles the codex and codex responses api proxy
binaries for MacOS. this target is signed and notarized.

Verified by triggering a build here:
https://github.com/openai/codex/actions/runs/20318136302/job/58367155205.
Downloaded the artifact and verified that the dmg is signed and
notarized, and the codex binary contained works as expected.
2025-12-18 11:19:10 -08:00
Jeremy Rose
be274cbe62 tui: improve rendering of search cell (#8273)
before:

<img width="795" height="150" alt="Screenshot 2025-12-18 at 10 48 01 AM"
src="https://github.com/user-attachments/assets/6f4d8856-b4c2-4e2a-b60a-b86f82b956a0"
/>

after:

<img width="795" height="150" alt="Screenshot 2025-12-18 at 10 48 39 AM"
src="https://github.com/user-attachments/assets/dd0d167a-5d09-4bb7-9d36-95a2eb1aaa83"
/>
2025-12-18 11:05:26 -08:00
Ahmed Ibrahim
7157421daa splash screen (#8270)
# External (non-OpenAI) Pull Request Requirements

Before opening this Pull Request, please read the dedicated
"Contributing" markdown file or your PR may be closed:
https://github.com/openai/codex/blob/main/docs/contributing.md

If your PR conforms to our contribution guidelines, replace this text
with a detailed and high quality description of your changes.

Include a link to a bug report or enhancement request.
2025-12-18 10:59:53 -08:00
Michael Bolin
b903285746 feat: migrate to new constraint-based loading strategy (#8251)
This is a significant change to how layers of configuration are applied.
In particular, the `ConfigLayerStack` now has two important fields:

- `layers: Vec<ConfigLayerEntry>`
- `requirements: ConfigRequirements`

We merge `TomlValue`s across the layers, but they are subject to
`ConfigRequirements` before creating a `Config`.

How I would review this PR:

- start with `codex-rs/app-server-protocol/src/protocol/v2.rs` and note
the new variants added to the `ConfigLayerSource` enum:
`LegacyManagedConfigTomlFromFile` and `LegacyManagedConfigTomlFromMdm`
- note that `ConfigLayerSource` now has a `precedence()` method and
implements `PartialOrd`
- `codex-rs/core/src/config_loader/layer_io.rs` is responsible for
loading "admin" preferences from `/etc/codex/managed_config.toml` and
MDM. Because `/etc/codex/managed_config.toml` is now deprecated in favor
of `/etc/codex/requirements.toml` and `/etc/codex/config.toml`, we now
include some extra information on the `LoadedConfigLayers` returned in
`layer_io.rs`.
- `codex-rs/core/src/config_loader/mod.rs` has major changes to
`load_config_layers_state()`, which is what produces `ConfigLayerStack`.
The docstring has the new specification and describes the various layers
that will be loaded and the precedence order.
- It uses the information from `LoaderOverrides` "twice," both in the
spirit of legacy support:
- We use one instances to derive an instance of `ConfigRequirements`.
Currently, the only field in `managed_config.toml` that contributes to
`ConfigRequirements` is `approval_policy`. This PR introduces
`Constrained::allow_only()` to support this.
- We use a clone of `LoaderOverrides` to derive
`ConfigLayerSource::LegacyManagedConfigTomlFromFile` and
`ConfigLayerSource::LegacyManagedConfigTomlFromMdm` layers, as
appropriate. As before, this ends up being a "best effort" at enterprise
controls, but is enforcement is not guaranteed like it is for
`ConfigRequirements`.
- Now we only create a "user" layer if `$CODEX_HOME/config.toml` exists.
(Previously, a user layer was always created for `ConfigLayerStack`.)
- Similarly, we only add a "session flags" layer if there are CLI
overrides.
- `config_loader/state.rs` contains the updated implementation for
`ConfigLayerStack`. Note the public API is largely the same as before,
but the implementation is quite different. We leverage the fact that
`ConfigLayerSource` is now `PartialOrd` to ensure layers are in the
correct order.
- A `Config` constructed via `ConfigBuilder.build()` will use
`load_config_layers_state()` to create the `ConfigLayerStack` and use
the associated `ConfigRequirements` when constructing the `Config`
object.
- That said, a `Config` constructed via
`Config::load_from_base_config_with_overrides()` does _not_ yet use
`ConfigBuilder`, so it creates a `ConfigRequirements::default()` instead
of loading a proper `ConfigRequirements`. I will fix this in a
subsequent PR.

Then the following files are mostly test changes:

```
codex-rs/app-server/tests/suite/v2/config_rpc.rs
codex-rs/core/src/config/service.rs
codex-rs/core/src/config_loader/tests.rs
```

Again, because we do not always include "user" and "session flags"
layers when the contents are empty, `ConfigLayerStack` sometimes has
fewer layers than before (and the precedence order changed slightly),
which is the main reason integration tests changed.
2025-12-18 10:06:05 -08:00
squinlan-oai
425c8dc372 cloud: default to current branch in cloud exec (#7460)
## Summary
- add a shared git-ref resolver and use it for `codex cloud exec` and
TUI task submission
- expose a new `--branch` flag to override the git ref passed to cloud
tasks
- cover the git-ref resolution behavior with new async unit tests and
supporting dev dependencies

## Testing
- cargo test -p codex-cloud-tasks


------
[Codex
Task](https://chatgpt.com/codex/tasks/task_i_692decc6cbec8332953470ef063e11ab)

---------

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
Co-authored-by: Jeremy Rose <172423086+nornagon-openai@users.noreply.github.com>
Co-authored-by: Jeremy Rose <nornagon@openai.com>
2025-12-18 17:44:38 +00:00
jif-oai
aea47b6553 feat: add name to beta features (#8266)
Add a name to Beta features

<img width="906" height="153" alt="Screenshot 2025-12-18 at 16 42 49"
src="https://github.com/user-attachments/assets/d56f3519-0613-4d9a-ad4d-38b1a7eb125a"
/>
2025-12-18 16:59:46 +00:00
Ahmed Ibrahim
f084e5264b caribou (#8265)
Welcome caribou

<img width="1536" height="1024" alt="image"
src="https://github.com/user-attachments/assets/2a67b21f-40cf-4518-aee4-691af331ab50"
/>
2025-12-18 08:58:44 -08:00
Magson Leone dos Santos
4c9d589f14 docs: clarify codex resume --all (CWD column & filtering) (#8264)
This pull request makes a small update to the session picker
documentation for `codex resume`. The main change clarifies how to view
the original working directory (CWD) for sessions and when the Git
branch is shown.

- The session picker now displays the recorded Git branch when
available, and instructions are added for showing the original working
directory by using the `--all` flag, which also disables CWD filtering
and adds a `CWD` column.
2025-12-18 16:54:50 +00:00
Michael Bolin
deafead169 chore: prefer AsRef<Path> to &Path (#8249)
This is some minor API cleanup that will make it easier to use
`AbsolutePathBuf` in more places in a subsequent PR.
2025-12-18 08:50:13 -08:00
Ahmed Ibrahim
374d591311 chores: clean picker (#8232)
# External (non-OpenAI) Pull Request Requirements

Before opening this Pull Request, please read the dedicated
"Contributing" markdown file or your PR may be closed:
https://github.com/openai/codex/blob/main/docs/contributing.md

If your PR conforms to our contribution guidelines, replace this text
with a detailed and high quality description of your changes.

Include a link to a bug report or enhancement request.
2025-12-18 08:41:34 -08:00
Michael Bolin
9bf41e9262 chore: simplify loading of Mac-specific logic in config_loader (#8248)
Over in `config_loader/macos.rs`, we were doing this complicated `mod`
thing to expose one version of `load_managed_admin_config_layer()` for
Mac:


580c59aa9a/codex-rs/core/src/config_loader/macos.rs (L4-L5)

While exposing a trivial implementation for non-Mac:


580c59aa9a/codex-rs/core/src/config_loader/macos.rs (L110-L117)

That was being used like this:


580c59aa9a/codex-rs/core/src/config_loader/layer_io.rs (L47-L48)

This PR simplifies that callsite in `layer_io.rs` to just be:

```rust
    #[cfg(not(target_os = "macos"))]
    let managed_preferences = None;
```

And updates `config_loader/mod.rs` so we only pull in `macos.rs` on Mac:

```rust
#[cfg(target_os = "macos")]
mod macos;
```

This simplifies `macos.rs` considerably, though it looks like a big
change because everything gets unindented and reformatted because we can
drop the whole `mod native` thing now.




---
[//]: # (BEGIN SAPLING FOOTER)
Stack created with [Sapling](https://sapling-scm.com). Best reviewed
with [ReviewStack](https://reviewstack.dev/openai/codex/pull/8248).
* #8251
* #8249
* __->__ #8248
2025-12-18 07:35:16 -08:00
jif-oai
1cfacbf56d chore: add beta features (#8201) 2025-12-18 13:09:12 +00:00
jif-oai
cea76b85af nit: ui background terminals (#8255) 2025-12-18 10:34:10 +00:00
xl-openai
5c8d22138a Reintroduce feature flags for skills. (#8244)
1. Reintroduce feature flags for skills;
2. UI tweaks (truncate descriptions, better validation error display).
2025-12-18 01:14:11 -08:00
Ethan Phillips
e1deeefa0f Change "Team" to "Buisness" and add Education (#8221)
This pull request updates the ChatGPT login description in the
onboarding authentication widgets to clarify which plans include usage.
The description now lists "Business" rather than "Team" and adds
"Education" plans in addition to the previously mentioned plans.

I have read the CLA Document and I hereby sign the CLAs.

---------

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
2025-12-18 08:35:22 +00:00
Michael Bolin
580c59aa9a fix: introduce ConfigBuilder (#8235)
Introduce `ConfigBuilder` as an alternative to our existing `Config`
constructors.

I noticed that the existing constructors,
`Config::load_with_cli_overrides()` and
`Config::load_with_cli_overrides_and_harness_overrides()`, did not take
`codex_home` as a parameter, which can be a problem.

Historically, when Codex was purely a CLI, we wanted to be extra sure
that the creation of `codex_home` was always done via
`find_codex_home()`, so we did not expose `codex_home` as a parameter
when creating `Config` in business logic. But in integration tests,
`codex_home` nearly always needs to be configured (as a temp directory),
which is why callers would have to go through
`Config::load_from_base_config_with_overrides()` instead.

Now that the Codex harness also functions as an app server, which could
conceivably load multiple threads where `codex_home` is parameterized
differently in each one, I think it makes sense to make this
configurable. Going to a builder pattern makes it more flexible to
ensure an arbitrary permutation of options can be set when constructing
a `Config` while using the appropriate defaults for the options that
aren't set explicitly.

Ultimately, I think this should make it possible for us to make
`Config::load_from_base_config_with_overrides()` private because all
integration tests should be able to leverage `ConfigBuilder` instead.
Though there could be edge cases, so I'll pursue that migration after we
get through the current config overhaul.






---
[//]: # (BEGIN SAPLING FOOTER)
Stack created with [Sapling](https://sapling-scm.com). Best reviewed
with [ReviewStack](https://reviewstack.dev/openai/codex/pull/8235).
* #8237
* __->__ #8235
2025-12-17 23:45:09 -08:00
Gav Verma
50dafbc31b Make loading malformed skills fail-open (#8243)
Instead of failing to start Codex, clearly call out that N skills did
not load and provide warnings so that the user may fix them.

<img width="3548" height="874" alt="image"
src="https://github.com/user-attachments/assets/6ce041b2-1373-4007-a6dd-0194e58fafe4"
/>
2025-12-17 23:41:04 -08:00
xl-openai
da3869eeb6 Support SYSTEM skills. (#8220)
1. Remove PUBLIC skills and introduce SYSTEM skills embedded in the
binary and installed into $CODEX_HOME/skills/.system at startup.
2. Skills are now always enabled (feature flag removed).
3. Update skills/list to accept forceReload and plumb it through (not
used by clients yet).
2025-12-17 18:48:28 -08:00
Ahmed Ibrahim
6f102e18c4 Show migration link (#8228)
# External (non-OpenAI) Pull Request Requirements

Before opening this Pull Request, please read the dedicated
"Contributing" markdown file or your PR may be closed:
https://github.com/openai/codex/blob/main/docs/contributing.md

If your PR conforms to our contribution guidelines, replace this text
with a detailed and high quality description of your changes.

Include a link to a bug report or enhancement request.
2025-12-18 02:03:40 +00:00
Michael Bolin
a8797019a1 chore: cleanup Config instantiation codepaths (#8226)
This PR does various types of cleanup before I can proceed with more
ambitious changes to config loading.

First, I noticed duplicated code across these two methods:


774bd9e432/codex-rs/core/src/config/mod.rs (L314-L324)


774bd9e432/codex-rs/core/src/config/mod.rs (L334-L344)

This has now been consolidated in
`load_config_as_toml_with_cli_overrides()`.

Further, I noticed that `Config::load_with_cli_overrides()` took two
similar arguments:


774bd9e432/codex-rs/core/src/config/mod.rs (L308-L311)

The difference between `cli_overrides` and `overrides` was not
immediately obvious to me. At first glance, it appears that one should
be able to be expressed in terms of the other, but it turns out that
some fields of `ConfigOverrides` (such as `cwd` and
`codex_linux_sandbox_exe`) are, by design, not configurable via a
`.toml` file or a command-line `--config` flag.

That said, I discovered that many callers of
`Config::load_with_cli_overrides()` were passing
`ConfigOverrides::default()` for `overrides`, so I created two separate
methods:

- `Config::load_with_cli_overrides(cli_overrides: Vec<(String,
TomlValue)>)`
- `Config::load_with_cli_overrides_and_harness_overrides(cli_overrides:
Vec<(String, TomlValue)>, harness_overrides: ConfigOverrides)`

The latter has a long name, as it is _not_ what should be used in the
common case, so the extra typing is designed to draw attention to this
fact. I tried to update the existing callsites to use the shorter name,
where possible.

Further, in the cases where `ConfigOverrides` is used, usually only a
limited subset of fields are actually set, so I updated the declarations
to leverage `..Default::default()` where possible.
2025-12-17 18:01:17 -08:00
Ahmed Ibrahim
774bd9e432 feat: model picker (#8209)
# External (non-OpenAI) Pull Request Requirements

Before opening this Pull Request, please read the dedicated
"Contributing" markdown file or your PR may be closed:
https://github.com/openai/codex/blob/main/docs/contributing.md

If your PR conforms to our contribution guidelines, replace this text
with a detailed and high quality description of your changes.

Include a link to a bug report or enhancement request.
2025-12-17 16:12:35 -08:00
iceweasel-oai
25ecd0c2e4 speed and reliability improvements for setting reads ACLs (#8216)
- Batch read ACL creation for online/offline sandbox user
- creates a new ACL helper process that is long-lived and runs in the
background
- uses a mutex so that only one helper process is running at a time.
2025-12-17 15:27:52 -08:00
Ahmed Ibrahim
927a6acbea Load models from static file (#8153)
- Load models from static file as a fallback
- Make API users use this file directly
- Add tests to make sure updates to the file always serialize
2025-12-17 14:34:13 -08:00
iceweasel-oai
a9a7cf3488 download new windows binaries when staging npm package (#8203) 2025-12-17 13:34:32 -08:00
Shijie Rao
df35189366 feat: make list_models non-blocking (#8198)
### Summary
* Make `app_server.list_models` to be non-blocking and consumers (i.e.
extension) can manage the flow themselves.
* Force config to use remote models and therefore fetch codex-auto model
list.
2025-12-17 12:13:16 -08:00
Michael Bolin
1e9babe178 fix: PathBuf -> AbsolutePathBuf in ConfigToml struct (#8205)
We should not have any `PathBuf` fields in `ConfigToml` or any of the
transitive structs we include, as we should use `AbsolutePathBuf`
instead so that we do not have to keep track of the file from which
`ConfigToml` was loaded such that we need it to resolve relative paths
later when the values of `ConfigToml` are used.

I only found two instances of this: `experimental_instructions_file` and
`experimental_compact_prompt_file`. Incidentally, when these were
specified as relative paths, they were resolved against `cwd` rather
than `config.toml`'s parent, which seems wrong to me. I changed the
behavior so they are resolved against the parent folder of the
`config.toml` being parsed, which we get "for free" due to the
introduction of `AbsolutePathBufGuard ` in
https://github.com/openai/codex/pull/7796.

While it is not great to change the behavior of a released feature,
these fields are prefixed with `experimental_`, which I interpret to
mean we have the liberty to change the contract.

For reference:

- `experimental_instructions_file` was introduced in
https://github.com/openai/codex/pull/1803
- `experimental_compact_prompt_file` was introduced in
https://github.com/openai/codex/pull/5959
2025-12-17 12:08:18 -08:00
jif-oai
3d92b443b0 feat: add config to disable warnings around ghost snapshot (#8178) 2025-12-17 18:50:22 +00:00
jif-oai
167553f00d fix: session downgrade (#8196)
The problem is that the `tokio` task own an `Arc` reference of the
session and that this task only exit with the broadcast channel get
closed. But this never get closed if the session is not dropped. So it's
a snake biting his tail basically

The most notable result was that non of the `Drop` implementation were
triggered (temporary files, shell snapshots, session cleaning etc etc)
when closing the session (through a `/new` for example)

The fix is just to weaken the `Arc` and upgrade it on the fly
2025-12-17 10:44:39 -08:00
jif-oai
9f28c6251d fix: proper skills dir cleanup (#8194) 2025-12-17 18:31:03 +00:00
Shijie Rao
3702793882 chore: update listMcpServerStatus to be non-blocking (#8151)
### Summary
* Update `listMcpServerStatus` to be non-blocking by wrapping it with
tokio:spawn.
2025-12-17 10:11:02 -08:00
jif-oai
a2cc0032e0 chore: move back stuff out of beta program (#8199) 2025-12-17 17:58:47 +00:00
jif-oai
f74e0cda92 feat: unified exec footer (#8117)
# With `unified_exec`
Known tools are correctly casted
<img width="1150" height="312" alt="Screenshot 2025-12-16 at 19 27 28"
src="https://github.com/user-attachments/assets/24150ee5-e88d-461b-a459-483c24784196"
/>
If a session exit the turn, we render it with the "Ran ..."
<img width="1168" height="355" alt="Screenshot 2025-12-16 at 19 27 58"
src="https://github.com/user-attachments/assets/3f00b60c-2d57-4f9d-a201-9cc8388957cb"
/>
If a session does not exit during the turn, it is closed at the end of
the turn but this is not rendered
<img width="642" height="342" alt="Screenshot 2025-12-16 at 19 34 37"
src="https://github.com/user-attachments/assets/c2bd9283-7017-4915-ba73-c52199b0b28e"
/>

# Without `unified_exec`
No changes
<img width="740" height="603" alt="Screenshot 2025-12-16 at 19 31 21"
src="https://github.com/user-attachments/assets/ca5d90fe-a9b2-42ba-bcd7-3e98c4ed22e8"
/>
2025-12-17 17:12:04 +00:00
jif-oai
ac6ba286aa feat: experimental menu (#8071)
This will automatically render any `Stage::Beta` features.

The change only gets applied to the *next session*. This started as a
bug but actually this is a good thing to prevent out of distribution
push

<img width="986" height="288" alt="Screenshot 2025-12-15 at 15 38 35"
src="https://github.com/user-attachments/assets/78b7a71d-0e43-4828-a118-91c5237909c7"
/>


<img width="509" height="109" alt="Screenshot 2025-12-15 at 17 35 44"
src="https://github.com/user-attachments/assets/6933de52-9b66-4abf-b58b-a5f26d5747e2"
/>
2025-12-17 17:08:03 +00:00
gt-oai
9352c6b235 feat: Constrain values for approval_policy (#7778)
Constrain `approval_policy` through new `admin_policy` config.

This PR will:
1. Add a `admin_policy` section to config, with a single field (for now)
`allowed_approval_policies`. This list constrains the set of
user-settable `approval_policy`s.
2. Introduce a new `Constrained<T>` type, which combines a current value
and a validator function. The validator function ensures disallowed
values are not set.
3. Change the type of `approval_policy` on `Config` and
`SessionConfiguration` from `AskForApproval` to
`Constrained<AskForApproval>`. The validator function is set by the
values passed into `allowed_approval_policies`.
4. `GenericDisplayRow`: add a `disabled_reason: Option<String>`. When
set, it disables selection of the value and indicates as such in the
menu. This also makes it unselectable with arrow keys or numbers. This
is used in the `/approvals` menu.

Follow ups are:
1. Do the same thing to `sandbox_policy`.
2. Propagate the allowed set of values through app-server for the
extension (though already this should prevent app-server from setting
this values, it's just that we want to disable UI elements that are
unsettable).

Happy to split this PR up if you prefer, into the logical numbered areas
above. Especially if there are parts we want to gavel on separately
(e.g. admin_policy).

Disabled full access:
<img width="1680" height="380" alt="image"
src="https://github.com/user-attachments/assets/1fb61c8c-1fcb-4dc4-8355-2293edb52ba0"
/>

Disabled `--yolo` on startup:
<img width="749" height="76" alt="image"
src="https://github.com/user-attachments/assets/0a1211a0-6eb1-40d6-a1d7-439c41e94ddb"
/>

CODEX-4087
2025-12-17 16:19:27 +00:00
Michael Bolin
de3fa03e1c feat: change ConfigLayerName into a disjoint union rather than a simple enum (#8095)
This attempts to tighten up the types related to "config layers."
Currently, `ConfigLayerEntry` is defined as follows:


bef36f4ae7/codex-rs/core/src/config_loader/state.rs (L19-L25)

but the `source` field is a bit of a lie, as:

- for `ConfigLayerName::Mdm`, it is
`"com.openai.codex/config_toml_base64"`
- for `ConfigLayerName::SessionFlags`, it is `"--config"`
- for `ConfigLayerName::User`, it is `"config.toml"` (just the file
name, not the path to the `config.toml` on disk that was read)
- for `ConfigLayerName::System`, it seems like it is usually
`/etc/codex/managed_config.toml` in practice, though on Windows, it is
`%CODEX_HOME%/managed_config.toml`:


bef36f4ae7/codex-rs/core/src/config_loader/layer_io.rs (L84-L101)

All that is to say, in three out of the four `ConfigLayerName`, `source`
is a `PathBuf` that is not an absolute path (or even a true path).

This PR tries to uplevel things by eliminating `source` from
`ConfigLayerEntry` and turning `ConfigLayerName` into a disjoint union
named `ConfigLayerSource` that has the appropriate metadata for each
variant, favoring the use of `AbsolutePathBuf` where appropriate:

```rust
pub enum ConfigLayerSource {
    /// Managed preferences layer delivered by MDM (macOS only).
    #[serde(rename_all = "camelCase")]
    #[ts(rename_all = "camelCase")]
    Mdm { domain: String, key: String },
    /// Managed config layer from a file (usually `managed_config.toml`).
    #[serde(rename_all = "camelCase")]
    #[ts(rename_all = "camelCase")]
    System { file: AbsolutePathBuf },
    /// Session-layer overrides supplied via `-c`/`--config`.
    SessionFlags,
    /// User config layer from a file (usually `config.toml`).
    #[serde(rename_all = "camelCase")]
    #[ts(rename_all = "camelCase")]
    User { file: AbsolutePathBuf },
}
```
2025-12-17 08:13:59 -08:00
jif-oai
45c164a982 nit: doc (#8186) 2025-12-17 15:29:29 +00:00
jif-oai
2e7e4f6ea6 nit: drop dead branch with unified_exec tool (#8182) 2025-12-17 13:55:13 +00:00
jif-oai
0abaf1b57c nit: prevent race in event rendering (#8181) 2025-12-17 13:24:02 +00:00
jif-oai
2bf57674d6 fix: flaky test 6 (#8175) 2025-12-17 11:59:13 +00:00
jif-oai
813bdb9010 feat: fallback unified_exec to shell_command (#8075) 2025-12-17 10:29:45 +00:00
xl-openai
4897efcced Add public skills + improve repo skill discovery and error UX (#8098)
1. Adds SkillScope::Public end-to-end (core + protocol) and loads skills
from the public cache directory
2. Improves repo skill discovery by searching upward for the nearest
.codex/skills within a git repo
3. Deduplicates skills by name with deterministic ordering to avoid
duplicates across sources
4. Fixes garbled “Skill errors” overlay rendering by preventing pending
history lines from being injected during the modal
5. Updates the project docs “Skills” intro wording to avoid hardcoded
paths
2025-12-17 01:35:49 -08:00
jif-oai
2041b72da7 chore: dedup review result duplication (#8057) 2025-12-17 09:10:51 +00:00
Ahmed Ibrahim
ebd1099b39 fix the models script (#8163)
look at
[failure](https://github.com/openai/codex/actions/runs/20294685253/job/58285812472)
2025-12-16 23:16:54 -08:00
Dylan Hurd
ae3793eb5d chore(apply-patch) unicode scenario (#8141)
## Summary
Adds a unicode scenario, and fills in files on failing scenarios to
ensure directory state is unchanged, for completeness

## Testing
- [x] only changes tests
2025-12-16 22:40:22 -08:00
Celia Chen
70913effc3 [app-server] add new RawResponseItem v2 event (#8152)
``codex/event/raw_response_item` (v1) -> `rawResponseItem/completed`
(v1).

test client log:
````
< {
<   "method": "codex/event/raw_response_item",
<   "params": {
<     "conversationId": "019b29f7-b089-7140-a535-3fe681562c15",
<     "id": "0",
<     "msg": {
<       "item": {
<         "arguments": "{\"command\":\"sed -n '1,160p' Cargo.toml\",\"workdir\":\"/Users/celia/code/codex/codex-rs\"}",
<         "call_id": "call_DrqbdB2jPxezPWc19YVEEt3h",
<         "name": "shell_command",
<         "type": "function_call"
<       },
<       "type": "raw_response_item"
<     }
<   }
< }
< {
<   "method": "rawResponseItem/completed",
<   "params": {
<     "item": {
<       "arguments": "{\"command\":\"sed -n '1,160p' Cargo.toml\",\"workdir\":\"/Users/celia/code/codex/codex-rs\"}",
<       "call_id": "call_DrqbdB2jPxezPWc19YVEEt3h",
<       "name": "shell_command",
<       "type": "function_call"
<     },
<     "threadId": "019b29f7-b089-7140-a535-3fe681562c15",
<     "turnId": "0"
<   }
< }
```
2025-12-17 02:19:30 +00:00
Eric Traut
42b8f28ee8 Fixed resume matching to respect case insensitivity when using WSL mount points (#8000)
This fixes #7995
2025-12-16 16:27:38 -08:00
Ahmed Ibrahim
14d80c35a9 Add user_agent header (#8149)
add `user_agent` header and remove rust tool chain
2025-12-16 16:23:24 -08:00
iceweasel-oai
3a0d9bca64 include new windows binaries in npm package. (#8140)
The Windows Elevated Sandbox uses two new binaries:

codex-windows-sandbox-setup.exe
codex-command-runner.exe

This PR includes them when installing native deps and packaging for npm
2025-12-16 16:14:33 -08:00
Ahmed Ibrahim
cafcd60ef0 Add a workflow for a hardcoded version of models (#8118)
- Fetch the endpoint
- Make a PR
2025-12-16 15:39:36 -08:00
Shijie Rao
600d01b33a chore: update listMcpServers to listMcpServerStatus (#8114)
### Summary
* rename app server `listMcpServers` to `listMcpServerStatuses`.
2025-12-16 15:28:45 -08:00
Josh McKinney
3fbf379e02 docs: refine tui2 viewport roadmap (#8122)
Update the tui2 viewport/history design doc with current status and a
prioritized roadmap (scroll feel, selection/copy correctness, streaming
wrap polish, terminal integration, and longer-term per-cell
interactivity ideas).
2025-12-16 22:16:50 +00:00
Dylan Hurd
a3b137d093 chore(apply-patch) move invocation tests (#8111)
## Summary:
This PR is a pure copy and paste of tests from lib.rs into
invocation.rs, to colocate logic and tests.

## Testing
- [x] Purely a test refactor
2025-12-16 12:49:06 -08:00
Eric Traut
bbc5675974 Revert "chore: review in read-only (#7593)" (#8127)
This reverts commit 291b54a762.

This commit was intended to prevent the model from making code changes
during `/review`, which is sometimes does. Unfortunately, it has other
unintended side effects that cause `/review` to fail in a variety of
ways. See #8115 and #7815. We've therefore decided to revert this
change.
2025-12-16 12:01:54 -08:00
Conor Branagan
51865695e4 feat(sdk): add xhigh reasoning effort support to TypeScript SDK (#8108)
Add "xhigh" to the ModelReasoningEffort type to match the Rust backend
which already supports this reasoning level for models like
gpt-5.1-codex-max.
2025-12-16 11:32:27 -08:00
Koichi Shiraishi
3a32716e1c fix tui2 compile error (#8124)
I'm not sure if this fix is ​​correct for the intended change in #7601,
but at least the compilation error is fixed.

regression: #7601

```
error[E0004]: non-exhaustive patterns: `TuiEvent::Mouse(_)` not covered
   --> tui2/src/update_prompt.rs:57:19
    |
 57 |             match event {
    |                   ^^^^^ pattern `TuiEvent::Mouse(_)` not covered
    |
note: `TuiEvent` defined here
   --> tui2/src/tui.rs:122:10
    |
122 | pub enum TuiEvent {
    |          ^^^^^^^^
...
126 |     Mouse(crossterm::event::MouseEvent),
    |     ----- not covered
    = note: the matched value is of type `TuiEvent`
help: ensure that all possible cases are being handled by adding a match arm with a wildcard pattern or an explicit pattern as shown
    |
 64 ~                 },
 65 +                 TuiEvent::Mouse(_) => todo!()
    |
```

Signed-off-by: Koichi Shiraishi <zchee.io@gmail.com>
2025-12-16 11:31:55 -08:00
Salman Chishti
5ceeaa96b8 Upgrade GitHub Actions for Node 24 compatibility (#8102)
## Summary

Upgrade GitHub Actions to their latest versions to ensure compatibility
with Node 24, as Node 20 will reach end-of-life in April 2026.

## Changes

| Action | Old Version(s) | New Version | Release | Files |
|--------|---------------|-------------|---------|-------|
| `actions/setup-node` |
[`v5`](https://github.com/actions/setup-node/releases/tag/v5) |
[`v6`](https://github.com/actions/setup-node/releases/tag/v6) |
[Release](https://github.com/actions/setup-node/releases/tag/v6) |
ci.yml, rust-release.yml, sdk.yml, shell-tool-mcp-ci.yml,
shell-tool-mcp.yml |

## Context

Per [GitHub's
announcement](https://github.blog/changelog/2025-09-19-deprecation-of-node-20-on-github-actions-runners/),
Node 20 is being deprecated and runners will begin using Node 24 by
default starting March 4th, 2026.

### Why this matters

- **Node 20 EOL**: April 2026
- **Node 24 default**: March 4th, 2026
- **Action**: Update to latest action versions that support Node 24

### Security Note

Actions that were previously pinned to commit SHAs remain pinned to SHAs
(updated to the latest release SHA) to maintain the security benefits of
immutable references.

### Testing

These changes only affect CI/CD workflow configurations and should not
impact application functionality. The workflows should be tested by
running them on a branch before merging.
2025-12-16 11:31:25 -08:00
Shijie Rao
b27c702e83 chore: mac codesign refactor (#8085)
### Summary
Similar to our linux and windows codesign, moving mac codesign logic
into its own files.
2025-12-16 11:20:44 -08:00
Dylan Hurd
e290d48264 chore(apply-patch) move invocation parsing (#8110)
lib.rs has grown quite large, and mixes two responsibilities:
1. executing patch operations
2. parsing apply_patch invocations via a shell command

This PR splits out (2) into its own file, so we can work with it more
easily. We are explicitly NOT moving tests in this PR, to ensure
behavior stays the same and we can avoid losing coverage via merge
conflicts. Tests are moved in a subsequent PR.

---
[//]: # (BEGIN SAPLING FOOTER)
Stack created with [Sapling](https://sapling-scm.com). Best reviewed
with [ReviewStack](https://reviewstack.dev/openai/codex/pull/8110).
* #8111
* __->__ #8110
2025-12-16 10:30:59 -08:00
iceweasel-oai
3d14da9728 bug fixes and perf improvements for elevated sandbox setup (#8094)
a few fixes based on testing feedback:
* ensure cap_sid file is always written by elevated setup.
* always log to same file whether using elevated sandbox or not
* process potentially slow ACE write operations in parallel
* dedupe write roots so we don't double process any
* don't try to create read/write ACEs on the same directories, due to
race condition
2025-12-16 09:48:29 -08:00
jif-oai
b53889aed5 Revert "feat: unified exec footer" (#8109)
Reverts openai/codex#8067
2025-12-16 17:03:19 +00:00
jif-oai
d7482510b1 nit: trace span for regular task (#8053)
Logs are too spammy

---------

Co-authored-by: Anton Panasenko <apanasenko@openai.com>
2025-12-16 16:53:15 +00:00
jif-oai
021c9a60e5 feat: unified exec footer (#8067)
<img width="452" height="205" alt="Screenshot 2025-12-15 at 17 54 44"
src="https://github.com/user-attachments/assets/9ece0b1c-8387-4dfc-b883-c6a68ea1b663"
/>
2025-12-16 16:52:36 +00:00
jif-oai
c9f5b9a6df feat: do not compact on last user turn (#8060) 2025-12-16 15:36:33 +00:00
jif-oai
ae57e18947 feat: close unified_exec at end of turn (#8052) 2025-12-16 12:16:43 +00:00
sayan-oai
cf44511e77 refactor TUI event loop to enable dropping + recreating crossterm event stream (#7961)
Introduces an `EventBroker` between the crossterm `EventStream` source
and the consumers in the TUI. This enables dropping + recreating the
`crossterm_events` without invalidating the consumer.

Dropping and recreating the crossterm event stream enables us to fully
relinquish `stdin` while the app keeps running. If the stream is not
dropped, it will continue to read from `stdin` even when it is not
actively being polled, potentially stealing input from other processes.
See
[here](https://www.reddit.com/r/rust/comments/1f3o33u/myterious_crossterm_input_after_running_vim/?utm_source=chatgpt.com)
and [here](https://ratatui.rs/recipes/apps/spawn-vim/) for details.

### Tests
Added tests for new `EventBroker` setup, existing tests pass, tested
locally.
2025-12-16 01:14:03 -08:00
Michael Bolin
bef36f4ae7 feat: if .codex is a sub-folder of a writable root, then make it read-only to the sandbox (#8088)
In preparation for in-repo configuration support, this updates
`WritableRoot::get_writable_roots_with_cwd()` to include the `.codex`
subfolder in `WritableRoot.read_only_subpaths`, if it exists, as we
already do for `.git`.

As noted, currently, like `.git`, `.codex` will only be read-only under
macOS Seatbelt, but we plan to bring support to other OSes, as well.

Updated the integration test in `seatbelt.rs` so that it actually
attempts to run the generated Seatbelt commands, verifying that:

- trying to write to `.codex/config.toml` in a writable root fails
- trying to write to `.git/hooks/pre-commit` in a writable root fails
- trying to write to the writable root containing the `.codex` and
`.git` subfolders succeeds
2025-12-15 22:54:43 -08:00
Josh McKinney
f074e5706b refactor(tui2): make transcript line metadata explicit (#8089)
This is a pure refactor only change.

Replace the flattened transcript line metadata from `Option<(usize,
usize)>` to an explicit
`TranscriptLineMeta::{CellLine { cell_index, line_in_cell }, Spacer}`
enum.

This makes spacer rows unambiguous, removes “tuple semantics” from call
sites, and keeps the
scroll anchoring model clearer and aligned with the viewport/history
design notes.

Changes:
- Introduce `TranscriptLineMeta` and update `TranscriptScroll` helpers
to consume it.
- Update `App::build_transcript_lines` and downstream consumers
(scrolling, row classification, ANSI rendering).
- Refresh scrolling module docs to describe anchors + spacer semantics
in context.
- Add tests and docs about the behavior

Tests:
- just fmt
- cargo test -p codex-tui2 tui::scrolling

Manual testing:
- Scroll the inline transcript with mouse wheel + PgUp/PgDn/Home/End,
then resize the terminal while staying scrolled up; verify the same
anchored content stays in view and you don’t jump to bottom
unexpectedly.
- Create a gap case (multiple non-continuation cells) and scroll so a
blank spacer row is at/near the top; verify scrolling doesn’t get stuck
on spacers and still anchors to nearby real lines.
- Start a selection while the assistant is streaming; verify the view
stops auto-following, the selection stays on the intended content, and
subsequent scrolling still behaves normally.
- Exit the TUI and confirm scrollback rendering still styles user rows
as blocks (background padding) and non-user rows as expected.
2025-12-16 05:27:47 +00:00
Dylan Hurd
b9d1a087ee chore(shell_command) fix freeform timeout output (#7791)
## Summary
Adding an additional integration test for timeout_ms

## Testing
- [x] these are tests
2025-12-15 19:26:39 -08:00
Ahmed Ibrahim
c0a12b3952 feat: merge remote models instead of destructing (#7997)
- merge remote models instead of destructing
- make config values have more precedent over remote values
2025-12-15 18:02:35 -08:00
Ahmed Ibrahim
d802b18716 fix parallel tool calls (#7956) 2025-12-16 01:28:27 +00:00
Josh McKinney
b093565bfb WIP: Rework TUI viewport, history printing, and selection/copy (#7601)
> large behavior change to how the TUI owns its viewport, history, and
suspend behavior.
> Core model is in place; a few items are still being polished before
this is ready to merge.

We've moved this over to a new tui2 crate from being directly on the tui
crate.
To enable use --enable tui2 (or the equivalent in your config.toml). See
https://developers.openai.com/codex/local-config#feature-flags

Note that this serves as a baseline for the changes that we're making to
be applied rapidly. Tui2 may not track later changes in the main tui.
It's experimental and may not be where we land on things.

---

## Summary

This PR moves the Codex TUI off of “cooperating” with the terminal’s
scrollback and onto a model
where the in‑memory transcript is the single source of truth. The TUI
now owns scrolling, selection,
copy, and suspend/exit printing based on that transcript, and only
writes to terminal scrollback in
append‑only fashion on suspend/exit. It also fixes streaming wrapping so
streamed responses reflow
with the viewport, and introduces configuration to control whether we
print history on suspend or
only on exit.

High‑level goals:

- Ensure history is complete, ordered, and never silently dropped.
- Print each logical history cell at most once into scrollback, even
with resizes and suspends.
- Make scrolling, selection, and copy match the visible transcript, not
the terminal’s notion of
  scrollback.
- Keep suspend/alt‑screen behavior predictable across terminals.

---

## Core Design Changes

### Transcript & viewport ownership

- Treat the transcript as a list of **cells** (user prompts, agent
messages, system/info rows,
  streaming segments).
- On each frame:
- Compute a **transcript region** as “full terminal frame minus the
bottom input area”.
- Flatten all cells into visual lines plus metadata (which cell + which
line within that cell).
- Use scroll state to choose which visual line is at the top of the
region.
  - Clear that region and draw just the visible slice of lines.
- The terminal’s scrollback is no longer part of the live layout
algorithm; it is only ever written
  to when we decide to print history.

### User message styling

- User prompts now render as clear blocks with:
  - A blank padding line above and below.
- A full‑width background for every line in the block (including the
prompt line itself).
- The same block styling is used when we print history into scrollback,
so the transcript looks
consistent whether you are in the TUI or scrolling back after
exit/suspend.

---

## Scrolling, Mouse, Selection, and Copy

### Scrolling

- Scrolling is defined in terms of the flattened transcript lines:
  - Mouse wheel scrolls up/down by fixed line increments.
  - PgUp/PgDn/Home/End operate on the same scroll model.
- The footer shows:
  - Whether you are “following live output” vs “scrolled up”.
  - Current scroll position (line / total).
- When there is no history yet, the bottom pane is **pegged high** and
gradually moves down as the
  transcript fills, matching the existing UX.

### Selection

- Click‑and‑drag defines a **linear selection** over transcript
line/column coordinates, not raw
  screen rows.
- Selection is **content‑anchored**:
- When you scroll, the selection moves with the underlying lines instead
of sticking to a fixed
    Y position.
- This holds both when scrolling manually and when new content streams
in, as long as you are in
    “follow” mode.
- The selection only covers the “transcript text” area:
  - Left gutter/prefix (bullets, markers) is intentionally excluded.
- This keeps copy/paste cleaner and avoids including structural margin
characters.

### Copy (`Ctrl+Y`)

- Introduce a small clipboard abstraction (`ClipboardManager`‑style) and
use a cross‑platform
  clipboard crate under the hood.
- When `Ctrl+Y` is pressed and a non‑empty selection exists:
- Re‑render the transcript region off‑screen using the same wrapping as
the visible viewport.
- Walk the selected line/column range over that buffer to reconstruct
the exact text:
    - Includes spaces between words.
    - Preserves empty lines within the selection.
  - Send the resulting text to the system clipboard.
- Show a short status message in the footer indicating success/failure.
- Copy is **best‑effort**:
- Clipboard failures (headless environment, sandbox, remote sessions)
are handled gracefully via
    status messages; they do not crash the TUI.
- Copy does *not* insert a new history entry; it only affects the status
bar.

---

## Streaming and Wrapping

### Previous behavior

Previously, streamed markdown:

- Was wrapped at a fixed width **at commit time** inside the streaming
collector.
- Those wrapped `Line<'static>` values were then wrapped again at
display time.
- As a result, streamed paragraphs could not “un‑wrap” when the terminal
width increased; they were
  permanently split according to the width at the start of the stream.

### New behavior

This PR implements the first step from
`codex-rs/tui/streaming_wrapping_design.md`:

- Streaming collector is constructed **without** a fixed width for
wrapping.
  - It still:
    - Buffers the full markdown source for the current stream.
    - Commits only at newline boundaries.
    - Emits logical lines as new content becomes available.
- Agent message cells now wrap streamed content only at **display
time**, based on the current
  viewport width, just like non‑streaming messages.
- Consequences:
  - Streamed responses reflow correctly when the terminal is resized.
- Animation steps are per logical line instead of per “pre‑wrapped”
visual line; this makes some
commits slightly larger but keeps the behavior simple and predictable.

Streaming responses are still represented as a sequence of logical
history entries (first line +
continuations) and integrate with the same scrolling, selection, and
printing model.

---

## Printing History on Suspend and Exit

### High‑water mark and append‑only scrollback

- Introduce a **cell‑based high‑water mark** (`printed_history_cells`)
on the transcript:
- Represents “how many cells at the front of the transcript have already
been printed”.
  - Completely independent of wrapped line counts or terminal geometry.
- Whenever we print history (suspend or exit):
- Take the suffix of `transcript_cells` beyond `printed_history_cells`.
  - Render just that suffix into styled lines at the **current** width.
  - Write those lines to stdout.
  - Advance `printed_history_cells` to cover all cells we just printed.
- Older cells are never re‑rendered for scrollback. They stay in
whatever wrapping they had when
printed, which is acceptable as long as the logical content is present
once.

### Suspend (`Ctrl+Z`)

- On suspend:
  - Leave alt screen if active and restore normal terminal modes.
- Render the not‑yet‑printed suffix of the transcript and append it to
normal scrollback.
  - Advance the high‑water mark.
  - Suspend the process.
- On resume (`fg`):
  - Re‑enter the TUI mode (alt screen + input modes).
- Clear the viewport region and fully redraw from in‑memory transcript
and state.

This gives predictable behavior across terminals without trying to
maintain scrollback live.

### Exit

- On exit:
  - Render any remaining unprinted cells once and write them to stdout.
- Add an extra blank line after the final Codex history cell before
printing token usage, so the
    transcript and usage info are visually separated.
- If you never suspended, exit prints the entire transcript exactly
once.
- If you suspended one or more times, exit prints only the cells
appended after the last suspend.

---

## Configuration: Suspend Printing

This PR also adds configuration to control **when** we print history:

- New TUI config option to gate printing on suspend:
  - At minimum:
- `print_on_suspend = true` – current behavior: print new history at
each suspend *and* on exit.
    - `print_on_suspend = false` – only print on exit.
- Default is tuned to preserve current behavior, but this can be
revisited based on feedback.
- The config is respected in the suspend path:
- If disabled, suspend only restores terminal modes and stops rendering
but does not print new
    history.
  - Exit still prints the full not‑yet‑printed suffix once.

This keeps the core viewport logic agnostic to preference, while letting
users who care about
quiet scrollback opt out of suspend printing.

---

## Tradeoffs

What we gain:

- A single authoritative history model (the in‑memory transcript).
- Deterministic viewport rendering independent of terminal quirks.
- Suspend/exit flows that:
  - Print each logical history cell exactly once.
  - Work across resizes and different terminals.
  - Interact cleanly with alt screen and raw‑mode toggling.
- Consistent, content‑anchored scrolling, selection, and copy.
- Streaming messages that reflow correctly with the viewport width.

What we accept:

- Scrollback may contain older cells wrapped differently than newer
ones.
- Streaming responses appear in scrollback as a sequence of blocks
corresponding to their streaming
  structure, not as a single retroactively reflowed paragraph.
- We do not attempt to rewrite or reflow already‑printed scrollback.

For deeper rationale and diagrams, see
`docs/tui_viewport_and_history.md` and
`codex-rs/tui/streaming_wrapping_design.md`.

---

## Still to Do Before This PR Is Ready

These are scoped to this PR (not long‑term future work):

- [ ] **Streaming wrapping polish**
  - Double‑check all streaming paths use display‑time wrapping only.
  - Ensure tests cover resizing after streaming has started.

- [ ] **Suspend printing config**
- Finalize config shape and default (keep existing behavior vs opt‑out).
- Wire config through TUI startup and document it in the appropriate
config docs.

- [x] **Bottom pane positioning**
- Ensure the bottom pane is pegged high when there’s no history and
smoothly moves down as the
transcript fills, matching the current behavior across startup and
resume.

- [x] **Transcript mouse scrolling**
- Re‑enable wheel‑based transcript scrolling on top of the new scroll
model.
- Make sure mouse scroll does not get confused with “alternate scroll”
modes from terminals.

- [x] **Mouse selection vs streaming**
- When selection is active, stop auto‑scrolling on streaming so the
selection remains stable on
    the selected content.
- Ensure that when streaming continues after selection is cleared,
“follow latest output” mode
    resumes correctly.

- [ ] **Auto‑scroll during drag**
- While the user is dragging a selection, auto‑scroll when the cursor is
at/near the top or bottom
of the transcript viewport to allow selecting beyond the current visible
window.

- [ ] **Feature flag / rollout**
- Investigate gating the new viewport/history behavior behind a feature
flag for initial rollout,
so we can fall back to the old behavior if needed during early testing.

- [ ] **Before/after videos**
  - Capture short clips showing:
    - Scrolling (mouse + keys).
    - Selection and copy.
    - Streaming behavior under resize.
    - Suspend/resume and exit printing.
  - Use these to validate UX and share context in the PR discussion.
2025-12-15 17:20:53 -08:00
Owen Lin
412dd37956 chore(app-server): remove stubbed thread/compact API (#8086)
We want to rely on server-side auto-compaction instead of having the
client trigger context compaction manually. This API was stubbed as a
placeholder and never implemented.
2025-12-16 01:11:01 +00:00
Eric Traut
d9554c8191 Fixes mcp elicitation test that fails for me when run locally (#8020) 2025-12-15 16:23:04 -08:00
jif-oai
3ee5c40261 chore: persist comments in edit (#7931)
This PR makes sure that inline comment is preserved for mcp server
config and arbitrary key/value setPath config.

---------

Co-authored-by: celia-oai <celia@openai.com>
2025-12-15 16:05:49 -08:00
miraclebakelaser
f754b19e80 Fix: Detect Bun global install via path check (#8004)
## Summary
Restores ability to detect when Codex is installed globally via **Bun**,
which was broken by c3e4f920b4. Fixes
#8003.

Instead of relying on `npm_config_user_agent` (which is only set when
running via `bunx` or `bun run`), this adds a path-based check to see if
the CLI wrapper is located in Bun's global installation directory.

## Regression Context
Commit `c3e4f920b4e965085164d6ee0249a873ef96da77` removed the
`BUN_INSTALL` environment variable checks to prevent false positives.
However, this caused false negatives for genuine Bun global installs
because `detectPackageManager()` defaults to NPM when no signal is
found.

## Changes
- Updated `codex-cli/bin/codex.js` to check if `__dirname` contains
`.bun/install/global` (handles both POSIX and Windows paths).

## Verification
Verified by performing a global install of the patched CLI (v0.69.0 to
trigger the update prompt):

1. Packed the CLI using `npm pack` in `codex-cli/` to create a release
tarball.
2. Installed globally via Bun: `bun install -g
$(pwd)/openai-codex-0.0.0-dev.tgz`.
3. Ran `codex`, confirmed it detected Bun (banner showed `bun install -g
@openai/codex`), selected "Update now", and verified it correctly
spawned `bun install -g` instead of `npm`.
4. Confirmed the upgrade completed successfully using Bun.
<img width="1038" height="813" alt="verifying installation via bun"
src="https://github.com/user-attachments/assets/00c9301a-18f1-4440-aa95-82ccffba896c"
/>
5. Verified installations via npm are unaffected.
<img width="2090" height="842" alt="verifying installation via npm"
src="https://github.com/user-attachments/assets/ccb3e031-b85c-4bbe-bac7-23b087c5b844"
/>
2025-12-15 15:30:06 -08:00
Victor Vannara
fbeb7d47a9 chore(ci): drop Homebrew origin/main workaround for macOS runners (#8084)
## Notes

GitHub Actions macOS runners now ship a Homebrew version (5.0.5) that
includes the fix that was needed in a change, so it's possible to remove
the temporary CI step that forced using brew from origin/main (added in
#7680).

Proof of macOS GitHub Actions coming packaged with 5.0.5 - latest commit
on `main`
(https://github.com/openai/codex/actions/runs/20245177832/job/58123247999)
- <img width="1286" height="136" alt="image"
src="https://github.com/user-attachments/assets/8b25fd57-dad5-45c5-907c-4f4da6a36c3f"
/>

`actions/runner-images` upgraded the macOS 14 image from pre-release to
release today
(https://github.com/actions/runner-images/releases/tag/macos-14-arm64%2F20251210.0045)

- <img width="1076" height="793" alt="image"
src="https://github.com/user-attachments/assets/357ea4bd-40b0-49c3-a6cd-e7d87ba6766d"
/>
2025-12-15 15:29:43 -08:00
Lucas Kim
54def78a22 docs: fix gpt-5.2 typo in config.md (#8079)
Fix small typo in docs/config.md: `gpt5-2` -> `gpt-5.2`
2025-12-15 15:15:14 -08:00
Jeremy Rose
2c6995ca4d exec-server: additional context for errors (#7935)
Add a .context() on some exec-server errors for debugging CI flakes.

Also, "login": false in the test to make the test not affected by user
profile.
2025-12-15 11:40:40 -08:00
iceweasel-oai
b4635ccc07 better name for windows sandbox features (#8077)
`--enable enable...` is a bad look
2025-12-15 10:15:40 -08:00
Robby He
017a4a06b2 Fix: Skip Option<()> schema generation to avoid invalid Windows filenames (#7479) (#7969)
## Problem

When generating JSON schemas on Windows, the `codex app-server
generate-json-schema` command fails with a filename error:
```text
Error: Failed to write JSON schema for Option<()>
Caused by:
    0: Failed to write .\Option<()>.json
    1: The filename, directory name, or volume label syntax is incorrect. (os error 123)
```
This occurs because Windows doesn't allow certain characters in
filenames, specifically the angle brackets **<>** used in the
**Option<()>** type name.

## Root Cause

The schema generation process attempts to create individual JSON files
for each schema definition, including `Option<()>`. However, the
characters `<` and `>` are invalid in Windows filenames, causing the
file creation to fail.

## Solution

The fix extends the existing `IGNORED_DEFINITIONS` constant (which was
already being used in the **bundle generation**) to also skip
`Option<()>` when generating individual JSON schema files. This
maintains consistency with the existing behavior where `Option<()>` is
excluded from the bundled schema.

---

close #7479
2025-12-15 09:57:12 -08:00
iceweasel-oai
c696456bf1 stage new windows sandbox binaries as artifacts (#8076) 2025-12-15 09:15:32 -08:00
Eric Traut
5b472c933d Fixed formatting issue (#8069) 2025-12-15 06:18:33 -08:00
Mikhail Beliakov
4501c0ece4 Update config.md (#8066)
Update supporting docs with the actual options
2025-12-15 06:12:52 -08:00
jif-oai
0d9801d448 feat: ghost snapshot v2 (#8055)
This PR updates ghost snapshotting to avoid capturing oversized
untracked artifacts while keeping undo safe. Snapshot creation now
builds a temporary index from `git status --porcelain=2 -z`, writes a
tree and detached commit without touching refs, and records any ignored
large files/dirs in the snapshot report. Undo uses that metadata to
preserve large local artifacts while still cleaning up new transient
files.
2025-12-15 11:14:36 +01:00
jif-oai
4274e6189a feat: config ghost commits (#7873) 2025-12-15 09:13:06 +01:00
Pedro Batista
fc53411938 fix: Don't trigger keybindings view on input burst (#7980)
Human TL;DR - in some situations, pasting/rapidly inputting text will
currently cause `?` characters to be stripped from the input message
content, and display the key bindings helper. For instance, writing
"Where is X defined? Can we do Y?" results in "Where is X defined Can we
do Y" being added to the message draft area. This is mildly annoying.

The fix was a simple one line addition. Added a test, ran linters, and
all looks good to me. I didn't create an issue to link to in this PR - I
had submitted this bug as a report a while ago but can't seem to find it
now. Let me know if it's an absolute must for the PR to be accepted.

I have read the CLA Document and I hereby sign the CLA

Below is Codex's summary.

---

# `?` characters toggling shortcuts / being dropped

## Symptom

On Termux (and potentially other terminal environments), composing text
in the native input field and sending it to the TTY can cause:

- The shortcuts overlay to appear (as if `?` was pressed on an empty
prompt), and
- All of the literal `?` characters in the text to be **missing** from
the composer input,
  even when `?` is not the first character.

This typically happens when the composer was previously empty and the
terminal delivers the text as a rapid sequence of key events rather than
a single bracketed paste event.

## Root cause

The TUI has two relevant behaviors:

1. **Shortcut toggle on `?` when empty**
- `ChatComposer::handle_shortcut_overlay_key` treats a plain `?` press
as a toggle between the shortcut summary and the full shortcut overlay,
but only when the composer is empty.
- When it toggles, it consumes the key event (so `?` is *not* inserted
into the text input).

2. **“Paste burst” buffering for fast key streams**
- The TUI uses a heuristic to detect “paste-like” input bursts even when
the terminal doesn’t send an explicit paste event.
- During that burst detection, characters can be buffered (and the text
area can remain empty temporarily) while the system decides whether to
treat the stream as paste-like input.

In Termux’s “send composed text all at once” mode, the input often
arrives as a very fast stream of `KeyCode::Char(...)` events. While that
stream is being buffered as a burst, the visible textarea can still be
empty. If a `?` arrives during this window, it matches “empty composer”
and is interpreted as “toggle shortcuts” instead of “insert literal
`?`”, so the `?` is dropped.

## Fix

Make the `?` toggle conditional on not being in any paste-burst
transient state.

Implementation:

- `ChatComposer::handle_shortcut_overlay_key` now checks
`!self.is_in_paste_burst()` in addition to `self.is_empty()` before
toggling.
- This ensures that when input is arriving as a fast burst (including
the “pending first char” case), `?` is treated as normal text input
rather than a UI toggle.

## Test coverage

Added a test that simulates a Termux-like fast stream:

- Sends `h i ? t h e r e` as immediate successive `KeyEvent::Char`
events (no delays).
- Asserts that a paste burst is active and the textarea is still empty
while buffering.
- Flushes the burst and verifies:
  - The final text contains the literal `?` (`"hi?there"`), and
  - The footer mode is not `ShortcutOverlay`.

## Notes

This fix intentionally keeps the existing UX:

- `?` still toggles shortcuts when the composer is genuinely empty and
the user is not in the middle of entering text.
- `?` typed while composing content (including IME/native-input fast
streams) remains literal.
2025-12-14 23:54:59 -08:00
dependabot[bot]
adbbcb0a15 chore(deps): bump lru from 0.12.5 to 0.16.2 in /codex-rs (#8045)
Bumps [lru](https://github.com/jeromefroe/lru-rs) from 0.12.5 to 0.16.2.
<details>
<summary>Changelog</summary>
<p><em>Sourced from <a
href="https://github.com/jeromefroe/lru-rs/blob/master/CHANGELOG.md">lru's
changelog</a>.</em></p>
<blockquote>
<h2><a
href="https://github.com/jeromefroe/lru-rs/tree/0.16.2">v0.16.2</a> -
2025-10-14</h2>
<ul>
<li>Upgrade hashbrown dependency to 0.16.0.</li>
</ul>
<h2><a
href="https://github.com/jeromefroe/lru-rs/tree/0.16.1">v0.16.1</a> -
2025-09-08</h2>
<ul>
<li>Fix <code>Clone</code> for unbounded cache.</li>
</ul>
<h2><a
href="https://github.com/jeromefroe/lru-rs/tree/0.16.0">v0.16.0</a> -
2025-07-02</h2>
<ul>
<li>Implement <code>Clone</code> for caches with custom hashers.</li>
</ul>
<h2><a
href="https://github.com/jeromefroe/lru-rs/tree/0.15.0">v0.15.0</a> -
2025-06-26</h2>
<ul>
<li>Return bool from <code>promote</code> and <code>demote</code> to
indicate whether key was found.</li>
</ul>
<h2><a
href="https://github.com/jeromefroe/lru-rs/tree/0.14.0">v0.14.0</a> -
2025-04-12</h2>
<ul>
<li>Use <code>NonZeroUsize::MAX</code> instead of <code>unwrap()</code>,
and update MSRV to 1.70.0.</li>
</ul>
<h2><a
href="https://github.com/jeromefroe/lru-rs/tree/0.13.0">v0.13.0</a> -
2025-01-27</h2>
<ul>
<li>Add <code>peek_mru</code> and <code>pop_mru</code> methods, upgrade
dependency on <code>hashbrown</code> to 0.15.2, and update MSRV to
1.65.0.</li>
</ul>
</blockquote>
</details>
<details>
<summary>Commits</summary>
<ul>
<li><a
href="c1f843ded0"><code>c1f843d</code></a>
Merge pull request <a
href="https://redirect.github.com/jeromefroe/lru-rs/issues/223">#223</a>
from jeromefroe/jerome/prepare-0-16-2-release</li>
<li><a
href="fc4f30953e"><code>fc4f309</code></a>
Prepare 0.16.2 release</li>
<li><a
href="e91ea2bd85"><code>e91ea2b</code></a>
Merge pull request <a
href="https://redirect.github.com/jeromefroe/lru-rs/issues/222">#222</a>
from torokati44/hashbrown-0.16</li>
<li><a
href="90d05feff3"><code>90d05fe</code></a>
Update hashbrown to 0.16</li>
<li><a
href="c699209232"><code>c699209</code></a>
Merge pull request <a
href="https://redirect.github.com/jeromefroe/lru-rs/issues/220">#220</a>
from jeromefroe/jerome/prepare-0-16-1-release</li>
<li><a
href="2bd8207030"><code>2bd8207</code></a>
Prepare 0.16.1 release</li>
<li><a
href="1b21bf1c59"><code>1b21bf1</code></a>
Merge pull request <a
href="https://redirect.github.com/jeromefroe/lru-rs/issues/219">#219</a>
from wqfish/bk</li>
<li><a
href="3ec42b6369"><code>3ec42b6</code></a>
Fix clone implementation for unbounded cache</li>
<li><a
href="e2e3e47c33"><code>e2e3e47</code></a>
Merge pull request <a
href="https://redirect.github.com/jeromefroe/lru-rs/issues/218">#218</a>
from jeromefroe/jerome/prepare-0-16-0-release</li>
<li><a
href="17fe4f328a"><code>17fe4f3</code></a>
Prepare 0.16.0 release</li>
<li>Additional commits viewable in <a
href="https://github.com/jeromefroe/lru-rs/compare/0.12.5...0.16.2">compare
view</a></li>
</ul>
</details>
<br />


[![Dependabot compatibility
score](https://dependabot-badges.githubapp.com/badges/compatibility_score?dependency-name=lru&package-manager=cargo&previous-version=0.12.5&new-version=0.16.2)](https://docs.github.com/en/github/managing-security-vulnerabilities/about-dependabot-security-updates#about-compatibility-scores)

Dependabot will resolve any conflicts with this PR as long as you don't
alter it yourself. You can also trigger a rebase manually by commenting
`@dependabot rebase`.

[//]: # (dependabot-automerge-start)
[//]: # (dependabot-automerge-end)

---

<details>
<summary>Dependabot commands and options</summary>
<br />

You can trigger Dependabot actions by commenting on this PR:
- `@dependabot rebase` will rebase this PR
- `@dependabot recreate` will recreate this PR, overwriting any edits
that have been made to it
- `@dependabot merge` will merge this PR after your CI passes on it
- `@dependabot squash and merge` will squash and merge this PR after
your CI passes on it
- `@dependabot cancel merge` will cancel a previously requested merge
and block automerging
- `@dependabot reopen` will reopen this PR if it is closed
- `@dependabot close` will close this PR and stop Dependabot recreating
it. You can achieve the same result by closing it manually
- `@dependabot show <dependency name> ignore conditions` will show all
of the ignore conditions of the specified dependency
- `@dependabot ignore this major version` will close this PR and stop
Dependabot creating any more for this major version (unless you reopen
the PR or upgrade to it yourself)
- `@dependabot ignore this minor version` will close this PR and stop
Dependabot creating any more for this minor version (unless you reopen
the PR or upgrade to it yourself)
- `@dependabot ignore this dependency` will close this PR and stop
Dependabot creating any more for this dependency (unless you reopen the
PR or upgrade to it yourself)


</details>

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-12-14 22:32:15 -08:00
dependabot[bot]
3843cc7b34 chore(deps): bump sentry from 0.34.0 to 0.46.0 in /codex-rs (#8043)
Bumps [sentry](https://github.com/getsentry/sentry-rust) from 0.34.0 to
0.46.0.
<details>
<summary>Release notes</summary>
<p><em>Sourced from <a
href="https://github.com/getsentry/sentry-rust/releases">sentry's
releases</a>.</em></p>
<blockquote>
<h2>0.46.0</h2>
<h3>Breaking changes</h3>
<ul>
<li>Removed the <code>ClientOptions</code> struct's
<code>trim_backtraces</code> and <code>extra_border_frames</code> fields
(<a
href="https://redirect.github.com/getsentry/sentry-rust/pull/925">#925</a>).
<ul>
<li>These fields configured backtrace trimming, which is being removed
in this release.</li>
</ul>
</li>
</ul>
<h3>Improvements</h3>
<ul>
<li>Removed backtrace trimming to align the Rust SDK with the general
principle that Sentry SDKs should only truncate telemetry data when
needed to comply with <a
href="https://develop.sentry.dev/sdk/data-model/envelopes/#size-limits">documented
size limits</a> (<a
href="https://redirect.github.com/getsentry/sentry-rust/pull/925">#925</a>).
This change ensures that as much data as possible remains available for
debugging.
<ul>
<li>If you notice any new issues being created for existing errors after
this change, please open an issue on <a
href="https://github.com/getsentry/sentry-rust/issues/new/choose">GitHub</a>.</li>
</ul>
</li>
</ul>
<h3>Fixes</h3>
<ul>
<li>fix: adjust sentry.origin for log integration (<a
href="https://redirect.github.com/getsentry/sentry-rust/pull/919">#919</a>)
by <a href="https://github.com/lcian"><code>@​lcian</code></a></li>
</ul>
<h2>0.45.0</h2>
<h3>Breaking changes</h3>
<ul>
<li>Add custom variant to <code>AttachmentType</code> that holds an
arbitrary String. (<a
href="https://redirect.github.com/getsentry/sentry-rust/pull/916">#916</a>)</li>
</ul>
<h2>0.44.0</h2>
<h3>Breaking changes</h3>
<ul>
<li>feat(log): support combined LogFilters and RecordMappings (<a
href="https://redirect.github.com/getsentry/sentry-rust/pull/914">#914</a>)
by <a href="https://github.com/lcian"><code>@​lcian</code></a>
<ul>
<li>Breaking change: <code>sentry::integrations::log::LogFilter</code>
has been changed to a <code>bitflags</code> struct.</li>
<li>It's now possible to map a <code>log</code> record to multiple items
in Sentry by combining multiple log filters in the filter, e.g.
<code>log::Level::ERROR =&gt; LogFilter::Event |
LogFilter::Log</code>.</li>
<li>If using a custom <code>mapper</code> instead, it's possible to
return a
<code>Vec&lt;sentry::integrations::log::RecordMapping&gt;</code> to map
a <code>log</code> record to multiple items in Sentry.</li>
</ul>
</li>
</ul>
<h3>Behavioral changes</h3>
<ul>
<li>ref(log): send logs by default when logs feature flag is enabled (<a
href="https://redirect.github.com/getsentry/sentry-rust/pull/915">#915</a>)
by <a href="https://github.com/lcian"><code>@​lcian</code></a>
<ul>
<li>If the <code>logs</code> feature flag is enabled, the default Sentry
<code>log</code> logger now sends logs for all events at or above
INFO.</li>
</ul>
</li>
<li>ref(logs): enable logs by default if logs feature flag is used (<a
href="https://redirect.github.com/getsentry/sentry-rust/pull/910">#910</a>)
by <a href="https://github.com/lcian"><code>@​lcian</code></a>
<ul>
<li>This changes the default value of
<code>sentry::ClientOptions::enable_logs</code> to
<code>true</code>.</li>
<li>This simplifies the setup of Sentry structured logs by requiring
users to just add the <code>log</code> feature flag to the
<code>sentry</code> dependency to opt-in to sending logs.</li>
<li>When the <code>log</code> feature flag is enabled, the
<code>tracing</code> and <code>log</code> integrations will send
structured logs to Sentry for all logs/events at or above INFO level by
default.</li>
</ul>
</li>
</ul>
<h2>0.43.0</h2>
<h3>Breaking changes</h3>
<ul>
<li>ref(tracing): rework tracing to Sentry span name/op conversion (<a
href="https://redirect.github.com/getsentry/sentry-rust/pull/887">#887</a>)
by <a href="https://github.com/lcian"><code>@​lcian</code></a>
<ul>
<li>The <code>tracing</code> integration now uses the tracing span name
as the Sentry span name by default.</li>
<li>Before this change, the span name would be set based on the
<code>tracing</code> span target
(<code>&lt;module&gt;::&lt;function&gt;</code> when using the
<code>tracing::instrument</code> macro).</li>
<li>The <code>tracing</code> integration now uses <code>&lt;span
target&gt;::&lt;span name&gt;</code> as the default Sentry span op (i.e.
<code>&lt;module&gt;::&lt;function&gt;</code> when using
<code>tracing::instrument</code>).</li>
<li>Before this change, the span op would be set based on the
<code>tracing</code> span name.</li>
<li>Read below to learn how to customize the span name and op.</li>
<li>When upgrading, please ensure to adapt any queries, metrics or
dashboards to use the new span names/ops.</li>
</ul>
</li>
<li>ref(tracing): use standard code attributes (<a
href="https://redirect.github.com/getsentry/sentry-rust/pull/899">#899</a>)
by <a href="https://github.com/lcian"><code>@​lcian</code></a>
<ul>
<li>Logs now carry the attributes <code>code.module.name</code>,
<code>code.file.path</code> and <code>code.line.number</code>
standardized in OTEL to surface the respective information, in contrast
with the previously sent <code>tracing.module_path</code>,
<code>tracing.file</code> and <code>tracing.line</code>.</li>
</ul>
</li>
<li>fix(actix): capture only server errors (<a
href="https://redirect.github.com/getsentry/sentry-rust/pull/877">#877</a>)
by <a href="https://github.com/lcian"><code>@​lcian</code></a></li>
</ul>
<!-- raw HTML omitted -->
</blockquote>
<p>... (truncated)</p>
</details>
<details>
<summary>Changelog</summary>
<p><em>Sourced from <a
href="https://github.com/getsentry/sentry-rust/blob/master/CHANGELOG.md">sentry's
changelog</a>.</em></p>
<blockquote>
<h2>0.46.0</h2>
<h3>Breaking changes</h3>
<ul>
<li>Removed the <code>ClientOptions</code> struct's
<code>trim_backtraces</code> and <code>extra_border_frames</code> fields
(<a
href="https://redirect.github.com/getsentry/sentry-rust/pull/925">#925</a>).
<ul>
<li>These fields configured backtrace trimming, which is being removed
in this release.</li>
</ul>
</li>
</ul>
<h3>Improvements</h3>
<ul>
<li>Removed backtrace trimming to align the Rust SDK with the general
principle that Sentry SDKs should only truncate telemetry data when
needed to comply with <a
href="https://develop.sentry.dev/sdk/data-model/envelopes/#size-limits">documented
size limits</a> (<a
href="https://redirect.github.com/getsentry/sentry-rust/pull/925">#925</a>).
This change ensures that as much data as possible remains available for
debugging.
<ul>
<li>If you notice any new issues being created for existing errors after
this change, please open an issue on <a
href="https://github.com/getsentry/sentry-rust/issues/new/choose">GitHub</a>.</li>
</ul>
</li>
</ul>
<h3>Fixes</h3>
<ul>
<li>fix: adjust sentry.origin for log integration (<a
href="https://redirect.github.com/getsentry/sentry-rust/pull/919">#919</a>)
by <a href="https://github.com/lcian"><code>@​lcian</code></a></li>
</ul>
<h2>0.45.0</h2>
<h3>Breaking changes</h3>
<ul>
<li>Add custom variant to <code>AttachmentType</code> that holds an
arbitrary String. (<a
href="https://redirect.github.com/getsentry/sentry-rust/pull/916">#916</a>)</li>
</ul>
<h2>0.44.0</h2>
<h3>Breaking changes</h3>
<ul>
<li>feat(log): support combined LogFilters and RecordMappings (<a
href="https://redirect.github.com/getsentry/sentry-rust/pull/914">#914</a>)
by <a href="https://github.com/lcian"><code>@​lcian</code></a>
<ul>
<li>Breaking change: <code>sentry::integrations::log::LogFilter</code>
has been changed to a <code>bitflags</code> struct.</li>
<li>It's now possible to map a <code>log</code> record to multiple items
in Sentry by combining multiple log filters in the filter, e.g.
<code>log::Level::ERROR =&gt; LogFilter::Event |
LogFilter::Log</code>.</li>
<li>If using a custom <code>mapper</code> instead, it's possible to
return a
<code>Vec&lt;sentry::integrations::log::RecordMapping&gt;</code> to map
a <code>log</code> record to multiple items in Sentry.</li>
</ul>
</li>
</ul>
<h3>Behavioral changes</h3>
<ul>
<li>ref(log): send logs by default when logs feature flag is enabled (<a
href="https://redirect.github.com/getsentry/sentry-rust/pull/915">#915</a>)
by <a href="https://github.com/lcian"><code>@​lcian</code></a>
<ul>
<li>If the <code>logs</code> feature flag is enabled, the default Sentry
<code>log</code> logger now sends logs for all events at or above
INFO.</li>
</ul>
</li>
<li>ref(logs): enable logs by default if logs feature flag is used (<a
href="https://redirect.github.com/getsentry/sentry-rust/pull/910">#910</a>)
by <a href="https://github.com/lcian"><code>@​lcian</code></a>
<ul>
<li>This changes the default value of
<code>sentry::ClientOptions::enable_logs</code> to
<code>true</code>.</li>
<li>This simplifies the setup of Sentry structured logs by requiring
users to just add the <code>log</code> feature flag to the
<code>sentry</code> dependency to opt-in to sending logs.</li>
<li>When the <code>log</code> feature flag is enabled, the
<code>tracing</code> and <code>log</code> integrations will send
structured logs to Sentry for all logs/events at or above INFO level by
default.</li>
</ul>
</li>
</ul>
<h2>0.43.0</h2>
<h3>Breaking changes</h3>
<ul>
<li>ref(tracing): rework tracing to Sentry span name/op conversion (<a
href="https://redirect.github.com/getsentry/sentry-rust/pull/887">#887</a>)
by <a href="https://github.com/lcian"><code>@​lcian</code></a>
<ul>
<li>The <code>tracing</code> integration now uses the tracing span name
as the Sentry span name by default.</li>
<li>Before this change, the span name would be set based on the
<code>tracing</code> span target
(<code>&lt;module&gt;::&lt;function&gt;</code> when using the
<code>tracing::instrument</code> macro).</li>
<li>The <code>tracing</code> integration now uses <code>&lt;span
target&gt;::&lt;span name&gt;</code> as the default Sentry span op (i.e.
<code>&lt;module&gt;::&lt;function&gt;</code> when using
<code>tracing::instrument</code>).</li>
<li>Before this change, the span op would be set based on the
<code>tracing</code> span name.</li>
<li>Read below to learn how to customize the span name and op.</li>
</ul>
</li>
</ul>
<!-- raw HTML omitted -->
</blockquote>
<p>... (truncated)</p>
</details>
<details>
<summary>Commits</summary>
<ul>
<li><a
href="8d82bfde59"><code>8d82bfd</code></a>
release: 0.46.0</li>
<li><a
href="9525735e5c"><code>9525735</code></a>
feat(backtrace): Stop truncating backtraces (<a
href="https://redirect.github.com/getsentry/sentry-rust/issues/925">#925</a>)</li>
<li><a
href="a57b91c5c8"><code>a57b91c</code></a>
ref: Fix new Clippy lints (<a
href="https://redirect.github.com/getsentry/sentry-rust/issues/935">#935</a>)</li>
<li><a
href="57595753d6"><code>5759575</code></a>
meta: Update cargo metadata (<a
href="https://redirect.github.com/getsentry/sentry-rust/issues/927">#927</a>)</li>
<li><a
href="77193f81e4"><code>77193f8</code></a>
chore: X handle update (<a
href="https://redirect.github.com/getsentry/sentry-rust/issues/926">#926</a>)</li>
<li><a
href="ca232686f4"><code>ca23268</code></a>
chore(ci): Migrate danger workflow from v2 to v3 (<a
href="https://redirect.github.com/getsentry/sentry-rust/issues/918">#918</a>)</li>
<li><a
href="2edf6d7a54"><code>2edf6d7</code></a>
fix: adjust sentry.origin for log integration (<a
href="https://redirect.github.com/getsentry/sentry-rust/issues/919">#919</a>)</li>
<li><a
href="6412048910"><code>6412048</code></a>
Merge branch 'release/0.45.0'</li>
<li><a
href="aa6d85b90f"><code>aa6d85b</code></a>
release: 0.45.0</li>
<li><a
href="b99eb46bcf"><code>b99eb46</code></a>
feat(types): Add custom variant to <code>AttachmentType</code> (<a
href="https://redirect.github.com/getsentry/sentry-rust/issues/916">#916</a>)</li>
<li>Additional commits viewable in <a
href="https://github.com/getsentry/sentry-rust/compare/0.34.0...0.46.0">compare
view</a></li>
</ul>
</details>
<br />


[![Dependabot compatibility
score](https://dependabot-badges.githubapp.com/badges/compatibility_score?dependency-name=sentry&package-manager=cargo&previous-version=0.34.0&new-version=0.46.0)](https://docs.github.com/en/github/managing-security-vulnerabilities/about-dependabot-security-updates#about-compatibility-scores)

Dependabot will resolve any conflicts with this PR as long as you don't
alter it yourself. You can also trigger a rebase manually by commenting
`@dependabot rebase`.

[//]: # (dependabot-automerge-start)
[//]: # (dependabot-automerge-end)

---

<details>
<summary>Dependabot commands and options</summary>
<br />

You can trigger Dependabot actions by commenting on this PR:
- `@dependabot rebase` will rebase this PR
- `@dependabot recreate` will recreate this PR, overwriting any edits
that have been made to it
- `@dependabot merge` will merge this PR after your CI passes on it
- `@dependabot squash and merge` will squash and merge this PR after
your CI passes on it
- `@dependabot cancel merge` will cancel a previously requested merge
and block automerging
- `@dependabot reopen` will reopen this PR if it is closed
- `@dependabot close` will close this PR and stop Dependabot recreating
it. You can achieve the same result by closing it manually
- `@dependabot show <dependency name> ignore conditions` will show all
of the ignore conditions of the specified dependency
- `@dependabot ignore this major version` will close this PR and stop
Dependabot creating any more for this major version (unless you reopen
the PR or upgrade to it yourself)
- `@dependabot ignore this minor version` will close this PR and stop
Dependabot creating any more for this minor version (unless you reopen
the PR or upgrade to it yourself)
- `@dependabot ignore this dependency` will close this PR and stop
Dependabot creating any more for this dependency (unless you reopen the
PR or upgrade to it yourself)


</details>

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-12-14 22:31:55 -08:00
dependabot[bot]
a21f0ac033 chore(deps): bump actions/cache from 4 to 5 (#8039)
Bumps [actions/cache](https://github.com/actions/cache) from 4 to 5.
<details>
<summary>Release notes</summary>
<p><em>Sourced from <a
href="https://github.com/actions/cache/releases">actions/cache's
releases</a>.</em></p>
<blockquote>
<h2>v5.0.0</h2>
<blockquote>
<p>[!IMPORTANT]
<strong><code>actions/cache@v5</code> runs on the Node.js 24 runtime and
requires a minimum Actions Runner version of
<code>2.327.1</code>.</strong></p>
<p>If you are using self-hosted runners, ensure they are updated before
upgrading.</p>
</blockquote>
<hr />
<h2>What's Changed</h2>
<ul>
<li>Upgrade to use node24 by <a
href="https://github.com/salmanmkc"><code>@​salmanmkc</code></a> in <a
href="https://redirect.github.com/actions/cache/pull/1630">actions/cache#1630</a></li>
<li>Prepare v5.0.0 release by <a
href="https://github.com/salmanmkc"><code>@​salmanmkc</code></a> in <a
href="https://redirect.github.com/actions/cache/pull/1684">actions/cache#1684</a></li>
</ul>
<p><strong>Full Changelog</strong>: <a
href="https://github.com/actions/cache/compare/v4.3.0...v5.0.0">https://github.com/actions/cache/compare/v4.3.0...v5.0.0</a></p>
<h2>v4.3.0</h2>
<h2>What's Changed</h2>
<ul>
<li>Add note on runner versions by <a
href="https://github.com/GhadimiR"><code>@​GhadimiR</code></a> in <a
href="https://redirect.github.com/actions/cache/pull/1642">actions/cache#1642</a></li>
<li>Prepare <code>v4.3.0</code> release by <a
href="https://github.com/Link"><code>@​Link</code></a>- in <a
href="https://redirect.github.com/actions/cache/pull/1655">actions/cache#1655</a></li>
</ul>
<h2>New Contributors</h2>
<ul>
<li><a href="https://github.com/GhadimiR"><code>@​GhadimiR</code></a>
made their first contribution in <a
href="https://redirect.github.com/actions/cache/pull/1642">actions/cache#1642</a></li>
</ul>
<p><strong>Full Changelog</strong>: <a
href="https://github.com/actions/cache/compare/v4...v4.3.0">https://github.com/actions/cache/compare/v4...v4.3.0</a></p>
<h2>v4.2.4</h2>
<h2>What's Changed</h2>
<ul>
<li>Update README.md by <a
href="https://github.com/nebuk89"><code>@​nebuk89</code></a> in <a
href="https://redirect.github.com/actions/cache/pull/1620">actions/cache#1620</a></li>
<li>Upgrade <code>@actions/cache</code> to <code>4.0.5</code> and move
<code>@protobuf-ts/plugin</code> to dev depdencies by <a
href="https://github.com/Link"><code>@​Link</code></a>- in <a
href="https://redirect.github.com/actions/cache/pull/1634">actions/cache#1634</a></li>
<li>Prepare release <code>4.2.4</code> by <a
href="https://github.com/Link"><code>@​Link</code></a>- in <a
href="https://redirect.github.com/actions/cache/pull/1636">actions/cache#1636</a></li>
</ul>
<h2>New Contributors</h2>
<ul>
<li><a href="https://github.com/nebuk89"><code>@​nebuk89</code></a> made
their first contribution in <a
href="https://redirect.github.com/actions/cache/pull/1620">actions/cache#1620</a></li>
</ul>
<p><strong>Full Changelog</strong>: <a
href="https://github.com/actions/cache/compare/v4...v4.2.4">https://github.com/actions/cache/compare/v4...v4.2.4</a></p>
<h2>v4.2.3</h2>
<h2>What's Changed</h2>
<ul>
<li>Update to use <code>@​actions/cache</code> 4.0.3 package &amp;
prepare for new release by <a
href="https://github.com/salmanmkc"><code>@​salmanmkc</code></a> in <a
href="https://redirect.github.com/actions/cache/pull/1577">actions/cache#1577</a>
(SAS tokens for cache entries are now masked in debug logs)</li>
</ul>
<h2>New Contributors</h2>
<ul>
<li><a href="https://github.com/salmanmkc"><code>@​salmanmkc</code></a>
made their first contribution in <a
href="https://redirect.github.com/actions/cache/pull/1577">actions/cache#1577</a></li>
</ul>
<p><strong>Full Changelog</strong>: <a
href="https://github.com/actions/cache/compare/v4.2.2...v4.2.3">https://github.com/actions/cache/compare/v4.2.2...v4.2.3</a></p>
<!-- raw HTML omitted -->
</blockquote>
<p>... (truncated)</p>
</details>
<details>
<summary>Changelog</summary>
<p><em>Sourced from <a
href="https://github.com/actions/cache/blob/main/RELEASES.md">actions/cache's
changelog</a>.</em></p>
<blockquote>
<h1>Releases</h1>
<h2>Changelog</h2>
<h3>5.0.1</h3>
<ul>
<li>Update <code>@azure/storage-blob</code> to <code>^12.29.1</code> via
<code>@actions/cache@5.0.1</code> <a
href="https://redirect.github.com/actions/cache/pull/1685">#1685</a></li>
</ul>
<h3>5.0.0</h3>
<blockquote>
<p>[!IMPORTANT]
<code>actions/cache@v5</code> runs on the Node.js 24 runtime and
requires a minimum Actions Runner version of <code>2.327.1</code>.
If you are using self-hosted runners, ensure they are updated before
upgrading.</p>
</blockquote>
<h3>4.3.0</h3>
<ul>
<li>Bump <code>@actions/cache</code> to <a
href="https://redirect.github.com/actions/toolkit/pull/2132">v4.1.0</a></li>
</ul>
<h3>4.2.4</h3>
<ul>
<li>Bump <code>@actions/cache</code> to v4.0.5</li>
</ul>
<h3>4.2.3</h3>
<ul>
<li>Bump <code>@actions/cache</code> to v4.0.3 (obfuscates SAS token in
debug logs for cache entries)</li>
</ul>
<h3>4.2.2</h3>
<ul>
<li>Bump <code>@actions/cache</code> to v4.0.2</li>
</ul>
<h3>4.2.1</h3>
<ul>
<li>Bump <code>@actions/cache</code> to v4.0.1</li>
</ul>
<h3>4.2.0</h3>
<p>TLDR; The cache backend service has been rewritten from the ground up
for improved performance and reliability. <a
href="https://github.com/actions/cache">actions/cache</a> now integrates
with the new cache service (v2) APIs.</p>
<p>The new service will gradually roll out as of <strong>February 1st,
2025</strong>. The legacy service will also be sunset on the same date.
Changes in these release are <strong>fully backward
compatible</strong>.</p>
<p><strong>We are deprecating some versions of this action</strong>. We
recommend upgrading to version <code>v4</code> or <code>v3</code> as
soon as possible before <strong>February 1st, 2025.</strong> (Upgrade
instructions below).</p>
<p>If you are using pinned SHAs, please use the SHAs of versions
<code>v4.2.0</code> or <code>v3.4.0</code></p>
<p>If you do not upgrade, all workflow runs using any of the deprecated
<a href="https://github.com/actions/cache">actions/cache</a> will
fail.</p>
<p>Upgrading to the recommended versions will not break your
workflows.</p>
<h3>4.1.2</h3>
<!-- raw HTML omitted -->
</blockquote>
<p>... (truncated)</p>
</details>
<details>
<summary>Commits</summary>
<ul>
<li><a
href="9255dc7a25"><code>9255dc7</code></a>
Merge pull request <a
href="https://redirect.github.com/actions/cache/issues/1686">#1686</a>
from actions/cache-v5.0.1-release</li>
<li><a
href="8ff5423e8b"><code>8ff5423</code></a>
chore: release v5.0.1</li>
<li><a
href="9233019a15"><code>9233019</code></a>
Merge pull request <a
href="https://redirect.github.com/actions/cache/issues/1685">#1685</a>
from salmanmkc/node24-storage-blob-fix</li>
<li><a
href="b975f2bb84"><code>b975f2b</code></a>
fix: add peer property to package-lock.json for dependencies</li>
<li><a
href="d0a0e18134"><code>d0a0e18</code></a>
fix: update license files for <code>@​actions/cache</code>,
fast-xml-parser, and strnum</li>
<li><a
href="74de208dcf"><code>74de208</code></a>
fix: update <code>@​actions/cache</code> to ^5.0.1 for Node.js 24
punycode fix</li>
<li><a
href="ac7f1152ea"><code>ac7f115</code></a>
peer</li>
<li><a
href="b0f846b50b"><code>b0f846b</code></a>
fix: update <code>@​actions/cache</code> with storage-blob fix for
Node.js 24 punycode depr...</li>
<li><a
href="a783357455"><code>a783357</code></a>
Merge pull request <a
href="https://redirect.github.com/actions/cache/issues/1684">#1684</a>
from actions/prepare-cache-v5-release</li>
<li><a
href="3bb0d78750"><code>3bb0d78</code></a>
docs: highlight v5 runner requirement in releases</li>
<li>Additional commits viewable in <a
href="https://github.com/actions/cache/compare/v4...v5">compare
view</a></li>
</ul>
</details>
<br />


[![Dependabot compatibility
score](https://dependabot-badges.githubapp.com/badges/compatibility_score?dependency-name=actions/cache&package-manager=github_actions&previous-version=4&new-version=5)](https://docs.github.com/en/github/managing-security-vulnerabilities/about-dependabot-security-updates#about-compatibility-scores)

Dependabot will resolve any conflicts with this PR as long as you don't
alter it yourself. You can also trigger a rebase manually by commenting
`@dependabot rebase`.

[//]: # (dependabot-automerge-start)
[//]: # (dependabot-automerge-end)

---

<details>
<summary>Dependabot commands and options</summary>
<br />

You can trigger Dependabot actions by commenting on this PR:
- `@dependabot rebase` will rebase this PR
- `@dependabot recreate` will recreate this PR, overwriting any edits
that have been made to it
- `@dependabot merge` will merge this PR after your CI passes on it
- `@dependabot squash and merge` will squash and merge this PR after
your CI passes on it
- `@dependabot cancel merge` will cancel a previously requested merge
and block automerging
- `@dependabot reopen` will reopen this PR if it is closed
- `@dependabot close` will close this PR and stop Dependabot recreating
it. You can achieve the same result by closing it manually
- `@dependabot show <dependency name> ignore conditions` will show all
of the ignore conditions of the specified dependency
- `@dependabot ignore this major version` will close this PR and stop
Dependabot creating any more for this major version (unless you reopen
the PR or upgrade to it yourself)
- `@dependabot ignore this minor version` will close this PR and stop
Dependabot creating any more for this minor version (unless you reopen
the PR or upgrade to it yourself)
- `@dependabot ignore this dependency` will close this PR and stop
Dependabot creating any more for this dependency (unless you reopen the
PR or upgrade to it yourself)


</details>

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-12-14 22:30:48 -08:00
dependabot[bot]
b349ec4e94 chore(deps): bump actions/download-artifact from 4 to 7 (#8037)
Bumps
[actions/download-artifact](https://github.com/actions/download-artifact)
from 4 to 7.
<details>
<summary>Release notes</summary>
<p><em>Sourced from <a
href="https://github.com/actions/download-artifact/releases">actions/download-artifact's
releases</a>.</em></p>
<blockquote>
<h2>v7.0.0</h2>
<h2>v7 - What's new</h2>
<blockquote>
<p>[!IMPORTANT]
actions/download-artifact@v7 now runs on Node.js 24 (<code>runs.using:
node24</code>) and requires a minimum Actions Runner version of 2.327.1.
If you are using self-hosted runners, ensure they are updated before
upgrading.</p>
</blockquote>
<h3>Node.js 24</h3>
<p>This release updates the runtime to Node.js 24. v6 had preliminary
support for Node 24, however this action was by default still running on
Node.js 20. Now this action by default will run on Node.js 24.</p>
<h2>What's Changed</h2>
<ul>
<li>Update GHES guidance to include reference to Node 20 version by <a
href="https://github.com/patrikpolyak"><code>@​patrikpolyak</code></a>
in <a
href="https://redirect.github.com/actions/download-artifact/pull/440">actions/download-artifact#440</a></li>
<li>Download Artifact Node24 support by <a
href="https://github.com/salmanmkc"><code>@​salmanmkc</code></a> in <a
href="https://redirect.github.com/actions/download-artifact/pull/415">actions/download-artifact#415</a></li>
<li>fix: update <code>@​actions/artifact</code> to fix Node.js 24
punycode deprecation by <a
href="https://github.com/salmanmkc"><code>@​salmanmkc</code></a> in <a
href="https://redirect.github.com/actions/download-artifact/pull/451">actions/download-artifact#451</a></li>
<li>prepare release v7.0.0 for Node.js 24 support by <a
href="https://github.com/salmanmkc"><code>@​salmanmkc</code></a> in <a
href="https://redirect.github.com/actions/download-artifact/pull/452">actions/download-artifact#452</a></li>
</ul>
<h2>New Contributors</h2>
<ul>
<li><a
href="https://github.com/patrikpolyak"><code>@​patrikpolyak</code></a>
made their first contribution in <a
href="https://redirect.github.com/actions/download-artifact/pull/440">actions/download-artifact#440</a></li>
<li><a href="https://github.com/salmanmkc"><code>@​salmanmkc</code></a>
made their first contribution in <a
href="https://redirect.github.com/actions/download-artifact/pull/415">actions/download-artifact#415</a></li>
</ul>
<p><strong>Full Changelog</strong>: <a
href="https://github.com/actions/download-artifact/compare/v6.0.0...v7.0.0">https://github.com/actions/download-artifact/compare/v6.0.0...v7.0.0</a></p>
<h2>v6.0.0</h2>
<h2>What's Changed</h2>
<p><strong>BREAKING CHANGE:</strong> this update supports Node
<code>v24.x</code>. This is not a breaking change per-se but we're
treating it as such.</p>
<ul>
<li>Update README for download-artifact v5 changes by <a
href="https://github.com/yacaovsnc"><code>@​yacaovsnc</code></a> in <a
href="https://redirect.github.com/actions/download-artifact/pull/417">actions/download-artifact#417</a></li>
<li>Update README with artifact extraction details by <a
href="https://github.com/yacaovsnc"><code>@​yacaovsnc</code></a> in <a
href="https://redirect.github.com/actions/download-artifact/pull/424">actions/download-artifact#424</a></li>
<li>Readme: spell out the first use of GHES by <a
href="https://github.com/danwkennedy"><code>@​danwkennedy</code></a> in
<a
href="https://redirect.github.com/actions/download-artifact/pull/431">actions/download-artifact#431</a></li>
<li>Bump <code>@actions/artifact</code> to <code>v4.0.0</code></li>
<li>Prepare <code>v6.0.0</code> by <a
href="https://github.com/danwkennedy"><code>@​danwkennedy</code></a> in
<a
href="https://redirect.github.com/actions/download-artifact/pull/438">actions/download-artifact#438</a></li>
</ul>
<h2>New Contributors</h2>
<ul>
<li><a
href="https://github.com/danwkennedy"><code>@​danwkennedy</code></a>
made their first contribution in <a
href="https://redirect.github.com/actions/download-artifact/pull/431">actions/download-artifact#431</a></li>
</ul>
<p><strong>Full Changelog</strong>: <a
href="https://github.com/actions/download-artifact/compare/v5...v6.0.0">https://github.com/actions/download-artifact/compare/v5...v6.0.0</a></p>
<h2>v5.0.0</h2>
<h2>What's Changed</h2>
<ul>
<li>Update README.md by <a
href="https://github.com/nebuk89"><code>@​nebuk89</code></a> in <a
href="https://redirect.github.com/actions/download-artifact/pull/407">actions/download-artifact#407</a></li>
<li>BREAKING fix: inconsistent path behavior for single artifact
downloads by ID by <a
href="https://github.com/GrantBirki"><code>@​GrantBirki</code></a> in <a
href="https://redirect.github.com/actions/download-artifact/pull/416">actions/download-artifact#416</a></li>
</ul>
<h2>v5.0.0</h2>
<h3>🚨 Breaking Change</h3>
<p>This release fixes an inconsistency in path behavior for single
artifact downloads by ID. <strong>If you're downloading single artifacts
by ID, the output path may change.</strong></p>
<h4>What Changed</h4>
<!-- raw HTML omitted -->
</blockquote>
<p>... (truncated)</p>
</details>
<details>
<summary>Commits</summary>
<ul>
<li><a
href="37930b1c2a"><code>37930b1</code></a>
Merge pull request <a
href="https://redirect.github.com/actions/download-artifact/issues/452">#452</a>
from actions/download-artifact-v7-release</li>
<li><a
href="72582b9e0a"><code>72582b9</code></a>
doc: update readme</li>
<li><a
href="0d2ec9d4cb"><code>0d2ec9d</code></a>
chore: release v7.0.0 for Node.js 24 support</li>
<li><a
href="fd7ae8fda6"><code>fd7ae8f</code></a>
Merge pull request <a
href="https://redirect.github.com/actions/download-artifact/issues/451">#451</a>
from actions/fix-storage-blob</li>
<li><a
href="d484700543"><code>d484700</code></a>
chore: restore minimatch.dep.yml license file</li>
<li><a
href="03a808050e"><code>03a8080</code></a>
chore: remove obsolete dependency license files</li>
<li><a
href="56fe6d904b"><code>56fe6d9</code></a>
chore: update <code>@​actions/artifact</code> license file to 5.0.1</li>
<li><a
href="8e3ebc4ab4"><code>8e3ebc4</code></a>
chore: update package-lock.json with <code>@​actions/artifact</code><a
href="https://github.com/5"><code>@​5</code></a>.0.1</li>
<li><a
href="1e3c4b4d49"><code>1e3c4b4</code></a>
fix: update <code>@​actions/artifact</code> to ^5.0.0 for Node.js 24
punycode fix</li>
<li><a
href="458627d354"><code>458627d</code></a>
chore: use local <code>@​actions/artifact</code> package for Node.js 24
testing</li>
<li>Additional commits viewable in <a
href="https://github.com/actions/download-artifact/compare/v4...v7">compare
view</a></li>
</ul>
</details>
<br />


[![Dependabot compatibility
score](https://dependabot-badges.githubapp.com/badges/compatibility_score?dependency-name=actions/download-artifact&package-manager=github_actions&previous-version=4&new-version=7)](https://docs.github.com/en/github/managing-security-vulnerabilities/about-dependabot-security-updates#about-compatibility-scores)

Dependabot will resolve any conflicts with this PR as long as you don't
alter it yourself. You can also trigger a rebase manually by commenting
`@dependabot rebase`.

[//]: # (dependabot-automerge-start)
[//]: # (dependabot-automerge-end)

---

<details>
<summary>Dependabot commands and options</summary>
<br />

You can trigger Dependabot actions by commenting on this PR:
- `@dependabot rebase` will rebase this PR
- `@dependabot recreate` will recreate this PR, overwriting any edits
that have been made to it
- `@dependabot merge` will merge this PR after your CI passes on it
- `@dependabot squash and merge` will squash and merge this PR after
your CI passes on it
- `@dependabot cancel merge` will cancel a previously requested merge
and block automerging
- `@dependabot reopen` will reopen this PR if it is closed
- `@dependabot close` will close this PR and stop Dependabot recreating
it. You can achieve the same result by closing it manually
- `@dependabot show <dependency name> ignore conditions` will show all
of the ignore conditions of the specified dependency
- `@dependabot ignore this major version` will close this PR and stop
Dependabot creating any more for this major version (unless you reopen
the PR or upgrade to it yourself)
- `@dependabot ignore this minor version` will close this PR and stop
Dependabot creating any more for this minor version (unless you reopen
the PR or upgrade to it yourself)
- `@dependabot ignore this dependency` will close this PR and stop
Dependabot creating any more for this dependency (unless you reopen the
PR or upgrade to it yourself)


</details>

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-12-14 22:30:23 -08:00
Eric Traut
1e3cad95c0 Do not panic when session contains a tool call without an output (#8048)
Normally, all tool calls within a saved session should have a response,
but there are legitimate reasons for the response to be missing. This
can occur if the user canceled the call or there was an error of some
sort during the rollout. We shouldn't panic in this case.

This is a partial fix for #7990
2025-12-14 22:16:49 -08:00
dependabot[bot]
d39477ac06 chore(deps): bump socket2 from 0.6.0 to 0.6.1 in /codex-rs (#8046)
Bumps [socket2](https://github.com/rust-lang/socket2) from 0.6.0 to
0.6.1.
<details>
<summary>Changelog</summary>
<p><em>Sourced from <a
href="https://github.com/rust-lang/socket2/blob/master/CHANGELOG.md">socket2's
changelog</a>.</em></p>
<blockquote>
<h1>0.6.1</h1>
<h2>Added</h2>
<ul>
<li>Added support for Windows Registered I/O (RIO)
(<a
href="https://redirect.github.com/rust-lang/socket2/pull/604">rust-lang/socket2#604</a>).</li>
<li>Added support for <code>TCP_NOTSENT_LOWAT</code> on Linux via
<code>Socket::(set_)tcp_notsent_lowat</code>
(<a
href="https://redirect.github.com/rust-lang/socket2/pull/611">rust-lang/socket2#611</a>).</li>
<li>Added support for <code>SO_BUSY_POLL</code> on Linux via
<code>Socket::set_busy_poll</code>
(<a
href="https://redirect.github.com/rust-lang/socket2/pull/607">rust-lang/socket2#607</a>).</li>
<li><code>SockFilter::new</code> is now a const function
(<a
href="https://redirect.github.com/rust-lang/socket2/pull/609">rust-lang/socket2#609</a>).</li>
</ul>
<h2>Changed</h2>
<ul>
<li>Updated the windows-sys dependency to version 0.60
(<a
href="https://redirect.github.com/rust-lang/socket2/pull/605">rust-lang/socket2#605</a>).</li>
</ul>
</blockquote>
</details>
<details>
<summary>Commits</summary>
<ul>
<li><a
href="d0ba3d39a6"><code>d0ba3d3</code></a>
Release v0.6.1</li>
<li><a
href="3a8b7edda3"><code>3a8b7ed</code></a>
Add example to create <code>SockAddr</code> from
<code>libc::sockaddr_storage</code> (<a
href="https://redirect.github.com/rust-lang/socket2/issues/615">#615</a>)</li>
<li><a
href="b54e2e6dbf"><code>b54e2e6</code></a>
Disable armv7-sony-vita-newlibeabihf CI check</li>
<li><a
href="2d4a2f7b3b"><code>2d4a2f7</code></a>
Update feature <code>doc_auto_cfg</code> to <code>doc_cfg</code></li>
<li><a
href="11aa1029f2"><code>11aa102</code></a>
Add missing components when installing Rust in CI</li>
<li><a
href="528ba2b0da"><code>528ba2b</code></a>
Add TCP_NOTSENT_LOWAT socketopt support</li>
<li><a
href="1fdd2938c1"><code>1fdd293</code></a>
Correct rename in CHANGELOG.md (<a
href="https://redirect.github.com/rust-lang/socket2/issues/610">#610</a>)</li>
<li><a
href="600ff0d246"><code>600ff0d</code></a>
Add support for Windows Registered I/O</li>
<li><a
href="f0836965a1"><code>f083696</code></a>
Allow <code>SockFilter::new</code> in const contexts</li>
<li><a
href="15ade5100c"><code>15ade51</code></a>
Refactor for cargo fmt</li>
<li>Additional commits viewable in <a
href="https://github.com/rust-lang/socket2/compare/v0.6.0...v0.6.1">compare
view</a></li>
</ul>
</details>
<br />


[![Dependabot compatibility
score](https://dependabot-badges.githubapp.com/badges/compatibility_score?dependency-name=socket2&package-manager=cargo&previous-version=0.6.0&new-version=0.6.1)](https://docs.github.com/en/github/managing-security-vulnerabilities/about-dependabot-security-updates#about-compatibility-scores)

Dependabot will resolve any conflicts with this PR as long as you don't
alter it yourself. You can also trigger a rebase manually by commenting
`@dependabot rebase`.

[//]: # (dependabot-automerge-start)
[//]: # (dependabot-automerge-end)

---

<details>
<summary>Dependabot commands and options</summary>
<br />

You can trigger Dependabot actions by commenting on this PR:
- `@dependabot rebase` will rebase this PR
- `@dependabot recreate` will recreate this PR, overwriting any edits
that have been made to it
- `@dependabot merge` will merge this PR after your CI passes on it
- `@dependabot squash and merge` will squash and merge this PR after
your CI passes on it
- `@dependabot cancel merge` will cancel a previously requested merge
and block automerging
- `@dependabot reopen` will reopen this PR if it is closed
- `@dependabot close` will close this PR and stop Dependabot recreating
it. You can achieve the same result by closing it manually
- `@dependabot show <dependency name> ignore conditions` will show all
of the ignore conditions of the specified dependency
- `@dependabot ignore this major version` will close this PR and stop
Dependabot creating any more for this major version (unless you reopen
the PR or upgrade to it yourself)
- `@dependabot ignore this minor version` will close this PR and stop
Dependabot creating any more for this minor version (unless you reopen
the PR or upgrade to it yourself)
- `@dependabot ignore this dependency` will close this PR and stop
Dependabot creating any more for this dependency (unless you reopen the
PR or upgrade to it yourself)


</details>

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-12-14 22:15:58 -08:00
dependabot[bot]
dd68245a9d chore(deps): bump actions/upload-artifact from 5 to 6 (#8038)
Bumps
[actions/upload-artifact](https://github.com/actions/upload-artifact)
from 5 to 6.
<details>
<summary>Release notes</summary>
<p><em>Sourced from <a
href="https://github.com/actions/upload-artifact/releases">actions/upload-artifact's
releases</a>.</em></p>
<blockquote>
<h2>v6.0.0</h2>
<h2>v6 - What's new</h2>
<blockquote>
<p>[!IMPORTANT]
actions/upload-artifact@v6 now runs on Node.js 24 (<code>runs.using:
node24</code>) and requires a minimum Actions Runner version of 2.327.1.
If you are using self-hosted runners, ensure they are updated before
upgrading.</p>
</blockquote>
<h3>Node.js 24</h3>
<p>This release updates the runtime to Node.js 24. v5 had preliminary
support for Node.js 24, however this action was by default still running
on Node.js 20. Now this action by default will run on Node.js 24.</p>
<h2>What's Changed</h2>
<ul>
<li>Upload Artifact Node 24 support by <a
href="https://github.com/salmanmkc"><code>@​salmanmkc</code></a> in <a
href="https://redirect.github.com/actions/upload-artifact/pull/719">actions/upload-artifact#719</a></li>
<li>fix: update <code>@​actions/artifact</code> for Node.js 24 punycode
deprecation by <a
href="https://github.com/salmanmkc"><code>@​salmanmkc</code></a> in <a
href="https://redirect.github.com/actions/upload-artifact/pull/744">actions/upload-artifact#744</a></li>
<li>prepare release v6.0.0 for Node.js 24 support by <a
href="https://github.com/salmanmkc"><code>@​salmanmkc</code></a> in <a
href="https://redirect.github.com/actions/upload-artifact/pull/745">actions/upload-artifact#745</a></li>
</ul>
<p><strong>Full Changelog</strong>: <a
href="https://github.com/actions/upload-artifact/compare/v5.0.0...v6.0.0">https://github.com/actions/upload-artifact/compare/v5.0.0...v6.0.0</a></p>
</blockquote>
</details>
<details>
<summary>Commits</summary>
<ul>
<li><a
href="b7c566a772"><code>b7c566a</code></a>
Merge pull request <a
href="https://redirect.github.com/actions/upload-artifact/issues/745">#745</a>
from actions/upload-artifact-v6-release</li>
<li><a
href="e516bc8500"><code>e516bc8</code></a>
docs: correct description of Node.js 24 support in README</li>
<li><a
href="ddc45ed9bc"><code>ddc45ed</code></a>
docs: update README to correct action name for Node.js 24 support</li>
<li><a
href="615b319bd2"><code>615b319</code></a>
chore: release v6.0.0 for Node.js 24 support</li>
<li><a
href="017748b48f"><code>017748b</code></a>
Merge pull request <a
href="https://redirect.github.com/actions/upload-artifact/issues/744">#744</a>
from actions/fix-storage-blob</li>
<li><a
href="38d4c7997f"><code>38d4c79</code></a>
chore: rebuild dist</li>
<li><a
href="7d27270e0c"><code>7d27270</code></a>
chore: add missing license cache files for <code>@​actions/core</code>,
<code>@​actions/io</code>, and mi...</li>
<li><a
href="5f643d3c94"><code>5f643d3</code></a>
chore: update license files for <code>@​actions/artifact</code><a
href="https://github.com/5"><code>@​5</code></a>.0.1 dependencies</li>
<li><a
href="1df1684032"><code>1df1684</code></a>
chore: update package-lock.json with <code>@​actions/artifact</code><a
href="https://github.com/5"><code>@​5</code></a>.0.1</li>
<li><a
href="b5b1a91840"><code>b5b1a91</code></a>
fix: update <code>@​actions/artifact</code> to ^5.0.0 for Node.js 24
punycode fix</li>
<li>Additional commits viewable in <a
href="https://github.com/actions/upload-artifact/compare/v5...v6">compare
view</a></li>
</ul>
</details>
<br />


[![Dependabot compatibility
score](https://dependabot-badges.githubapp.com/badges/compatibility_score?dependency-name=actions/upload-artifact&package-manager=github_actions&previous-version=5&new-version=6)](https://docs.github.com/en/github/managing-security-vulnerabilities/about-dependabot-security-updates#about-compatibility-scores)

Dependabot will resolve any conflicts with this PR as long as you don't
alter it yourself. You can also trigger a rebase manually by commenting
`@dependabot rebase`.

[//]: # (dependabot-automerge-start)
[//]: # (dependabot-automerge-end)

---

<details>
<summary>Dependabot commands and options</summary>
<br />

You can trigger Dependabot actions by commenting on this PR:
- `@dependabot rebase` will rebase this PR
- `@dependabot recreate` will recreate this PR, overwriting any edits
that have been made to it
- `@dependabot merge` will merge this PR after your CI passes on it
- `@dependabot squash and merge` will squash and merge this PR after
your CI passes on it
- `@dependabot cancel merge` will cancel a previously requested merge
and block automerging
- `@dependabot reopen` will reopen this PR if it is closed
- `@dependabot close` will close this PR and stop Dependabot recreating
it. You can achieve the same result by closing it manually
- `@dependabot show <dependency name> ignore conditions` will show all
of the ignore conditions of the specified dependency
- `@dependabot ignore this major version` will close this PR and stop
Dependabot creating any more for this major version (unless you reopen
the PR or upgrade to it yourself)
- `@dependabot ignore this minor version` will close this PR and stop
Dependabot creating any more for this minor version (unless you reopen
the PR or upgrade to it yourself)
- `@dependabot ignore this dependency` will close this PR and stop
Dependabot creating any more for this dependency (unless you reopen the
PR or upgrade to it yourself)


</details>

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-12-14 22:13:57 -08:00
Thibault Sottiaux
c3d5102f73 chore: fix tooltip typos and align tone (#8047) 2025-12-14 20:02:41 -08:00
Victor Vannara
7c6a47958a docs: document enabling experimental skills (#8024)
## Notes

Skills are behind the experimental `skills` feature flag (disabled by
default), but the skills guide didn't explain how to turn them on.

- Add an explicit enable section to `docs/skills.md` (config +
`--enable`)
- Add the skills flag to `docs/config.md` and `docs/example-config.md`
- Document the `/skills` slash command
2025-12-14 14:34:22 -08:00
xl-openai
5d77d4db6b Reimplement skills loading using SkillsManager + skills/list op. (#7914)
refactor the way we load and manage skills:
1. Move skill discovery/caching into SkillsManager and reuse it across
sessions.
2. Add the skills/list API (Op::ListSkills/SkillsListResponse) to fetch
skills for one or more cwds. Also update app-server for VSCE/App;
3. Trigger skills/list during session startup so UIs preload skills and
handle errors immediately.
2025-12-14 09:58:17 -08:00
Michael Bolin
a2c86e5d88 docs: update the docs for @openai/codex-shell-tool-mcp (#7962)
The existing version of `shell-tool-mcp/README.md` was not written in a
way that was meant to be consumed by end-users. This is now fixed.

Added `codex-rs/exec-server/README.md` for the more technical bits.
2025-12-13 09:44:26 -08:00
Eric Traut
1ad261d681 Changed default wrap algorithm from OptimalFit to FirstFit (#7960)
Codex identified this as the cause of a reported hang:
https://github.com/openai/codex/issues/7822. Apparently, the wrapping
algorithm we're using has known issues and bad worst-case behaviors when
OptimalFit is used on certain strings. It recommended switching to
FirstFit instead.
2025-12-12 21:47:37 -08:00
Josh McKinney
6ec2831b91 Sync tui2 with tui and keep dual-run glue (#7965)
- Copy latest tui sources into tui2
- Restore notifications, tests, and styles
- Keep codex-tui interop conversions and snapshots

The expected changes that are necessary to make this work are still in
place:

diff -ru codex-rs/tui codex-rs/tui2 --exclude='*.snap'
--exclude='*.snap.new'

```diff
diff -ru --ex codex-rs/tui/Cargo.toml codex-rs/tui2/Cargo.toml
--- codex-rs/tui/Cargo.toml	2025-12-12 16:39:12
+++ codex-rs/tui2/Cargo.toml	2025-12-12 17:31:01
@@ -1,15 +1,15 @@
 [package]
-name = "codex-tui"
+name = "codex-tui2"
 version.workspace = true
 edition.workspace = true
 license.workspace = true
 
 [[bin]]
-name = "codex-tui"
+name = "codex-tui2"
 path = "src/main.rs"
 
 [lib]
-name = "codex_tui"
+name = "codex_tui2"
 path = "src/lib.rs"
 
 [features]
@@ -42,6 +42,7 @@
 codex-login = { workspace = true }
 codex-protocol = { workspace = true }
 codex-utils-absolute-path = { workspace = true }
+codex-tui = { workspace = true }
 color-eyre = { workspace = true }
 crossterm = { workspace = true, features = ["bracketed-paste", "event-stream"] }
 derive_more = { workspace = true, features = ["is_variant"] }
diff -ru --ex codex-rs/tui/src/app.rs codex-rs/tui2/src/app.rs
--- codex-rs/tui/src/app.rs	2025-12-12 16:39:05
+++ codex-rs/tui2/src/app.rs	2025-12-12 17:30:36
@@ -69,6 +69,16 @@
     pub update_action: Option<UpdateAction>,
 }
 
+impl From<AppExitInfo> for codex_tui::AppExitInfo {
+    fn from(info: AppExitInfo) -> Self {
+        codex_tui::AppExitInfo {
+            token_usage: info.token_usage,
+            conversation_id: info.conversation_id,
+            update_action: info.update_action.map(Into::into),
+        }
+    }
+}
+
 fn session_summary(
     token_usage: TokenUsage,
     conversation_id: Option<ConversationId>,
Only in codex-rs/tui/src/bin: md-events.rs
Only in codex-rs/tui2/src/bin: md-events2.rs
diff -ru --ex codex-rs/tui/src/cli.rs codex-rs/tui2/src/cli.rs
--- codex-rs/tui/src/cli.rs	2025-11-19 13:40:42
+++ codex-rs/tui2/src/cli.rs	2025-12-12 17:30:43
@@ -88,3 +88,28 @@
     #[clap(skip)]
     pub config_overrides: CliConfigOverrides,
 }
+
+impl From<codex_tui::Cli> for Cli {
+    fn from(cli: codex_tui::Cli) -> Self {
+        Self {
+            prompt: cli.prompt,
+            images: cli.images,
+            resume_picker: cli.resume_picker,
+            resume_last: cli.resume_last,
+            resume_session_id: cli.resume_session_id,
+            resume_show_all: cli.resume_show_all,
+            model: cli.model,
+            oss: cli.oss,
+            oss_provider: cli.oss_provider,
+            config_profile: cli.config_profile,
+            sandbox_mode: cli.sandbox_mode,
+            approval_policy: cli.approval_policy,
+            full_auto: cli.full_auto,
+            dangerously_bypass_approvals_and_sandbox: cli.dangerously_bypass_approvals_and_sandbox,
+            cwd: cli.cwd,
+            web_search: cli.web_search,
+            add_dir: cli.add_dir,
+            config_overrides: cli.config_overrides,
+        }
+    }
+}
diff -ru --ex codex-rs/tui/src/main.rs codex-rs/tui2/src/main.rs
--- codex-rs/tui/src/main.rs	2025-12-12 16:39:05
+++ codex-rs/tui2/src/main.rs	2025-12-12 16:39:06
@@ -1,8 +1,8 @@
 use clap::Parser;
 use codex_arg0::arg0_dispatch_or_else;
 use codex_common::CliConfigOverrides;
-use codex_tui::Cli;
-use codex_tui::run_main;
+use codex_tui2::Cli;
+use codex_tui2::run_main;
 
 #[derive(Parser, Debug)]
 struct TopCli {
diff -ru --ex codex-rs/tui/src/update_action.rs codex-rs/tui2/src/update_action.rs
--- codex-rs/tui/src/update_action.rs	2025-11-19 11:11:47
+++ codex-rs/tui2/src/update_action.rs	2025-12-12 17:30:48
@@ -9,6 +9,20 @@
     BrewUpgrade,
 }
 
+impl From<UpdateAction> for codex_tui::update_action::UpdateAction {
+    fn from(action: UpdateAction) -> Self {
+        match action {
+            UpdateAction::NpmGlobalLatest => {
+                codex_tui::update_action::UpdateAction::NpmGlobalLatest
+            }
+            UpdateAction::BunGlobalLatest => {
+                codex_tui::update_action::UpdateAction::BunGlobalLatest
+            }
+            UpdateAction::BrewUpgrade => codex_tui::update_action::UpdateAction::BrewUpgrade,
+        }
+    }
+}
+
 impl UpdateAction {
     /// Returns the list of command-line arguments for invoking the update.
     pub fn command_args(self) -> (&'static str, &'static [&'static str]) {
```
2025-12-12 20:46:18 -08:00
Anton Panasenko
ad7b9d63c3 [codex] add otel tracing (#7844) 2025-12-12 17:07:17 -08:00
Josh McKinney
596fcd040f docs: remove blanket ban on unsigned integers (#7957)
Drop the AGENTS.md rule that forbids unsigned ints. The blanket guidance
causes unnecessary complexity in cases where values are naturally
unsigned, leading to extra clamping/conversion code instead of using
checked or saturating arithmetic where needed.
2025-12-12 17:01:56 -08:00
Michael Bolin
7c18f7b680 fix: include Error in log message (#7955)
This addresses post-merge feedback from
https://github.com/openai/codex/pull/7856.
2025-12-13 00:31:34 +00:00
Michael Bolin
b1905d3754 fix: added test helpers for platform-specific paths (#7954)
This addresses post-merge feedback from
https://github.com/openai/codex/pull/7856.
2025-12-13 00:14:12 +00:00
Michael Bolin
642b7566df fix: introduce AbsolutePathBuf as part of sandbox config (#7856)
Changes the `writable_roots` field of the `WorkspaceWrite` variant of
the `SandboxPolicy` enum from `Vec<PathBuf>` to `Vec<AbsolutePathBuf>`.
This is helpful because now callers can be sure the value is an absolute
path rather than a relative one. (Though when using an absolute path in
a Seatbelt config policy, we still have to _canonicalize_ it first.)

Because `writable_roots` can be read from a config file, it is important
that we are able to resolve relative paths properly using the parent
folder of the config file as the base path.
2025-12-12 15:25:22 -08:00
iceweasel-oai
3d07cd6c0c fix cargo build switch (#7948) 2025-12-12 14:31:09 -08:00
Ivan Murashko
c978b6e222 fix: restore MCP startup progress messages in TUI (fixes #7827) (#7828)
## Problem

The introduction of `notify_sandbox_state_change()` in #7112 caused a
regression where the blocking call in `Session::new()` waits for all MCP
servers to fully initialize before returning. This prevents the TUI
event loop from starting, resulting in `McpStartupUpdateEvent` messages
being emitted but never consumed or displayed. As a result, the app
appears to hang during startup, and users do not see the expected
"Booting MCP server: {name}" status line.

Issue: [#7827](https://github.com/openai/codex/issues/7827)

## Solution
This change moves sandbox state notification into each MCP server's
background initialization task. The notification is sent immediately
after the server transitions to the Ready state. This approach:
- Avoids blocking `Session::new()`, allowing the TUI event loop to start
promptly.
- Ensures each MCP server receives its sandbox state before handling any
tool calls.
- Restores the display of "Booting MCP server" status lines during
startup.

## Key Changes
- Added `ManagedClient::notify_sandbox_state()` method.
- Passed sandbox_state to `McpConnectionManager::initialize()`.
- Sends sandbox state notification in the background task after the
server reaches Ready status.
- Removed blocking notify_sandbox_state_change() methods.
- Added a chatwidget snapshot test for the "Booting MCP server" status
line.

## Regression Details

Regression was bisected to #7112, which introduced the blocking
behavior.

---------

Co-authored-by: Michael Bolin <bolinfest@gmail.com>
Co-authored-by: Michael Bolin <mbolin@openai.com>
2025-12-12 22:07:03 +00:00
Ahmed Ibrahim
54feceea46 support 1p (#7945)
# External (non-OpenAI) Pull Request Requirements

Before opening this Pull Request, please read the dedicated
"Contributing" markdown file or your PR may be closed:
https://github.com/openai/codex/blob/main/docs/contributing.md

If your PR conforms to our contribution guidelines, replace this text
with a detailed and high quality description of your changes.

Include a link to a bug report or enhancement request.
2025-12-12 13:36:20 -08:00
iceweasel-oai
4d2deb1098 Sign two additional exes for Windows (#7942)
The elevated sandbox ships two exes
* one for elevated setup of the sandbox
* one to actually run commands under the sandbox user.

This PR adds them to the windows signing step
2025-12-12 13:33:42 -08:00
Michael Bolin
9009490357 fix: use PowerShell to parse PowerShell (#7607)
Previous to this PR, we used a hand-rolled PowerShell parser in
`windows_safe_commands.rs` to take a `&str` of PowerShell script see if
it is equivalent to a list of `execvp(3)` invocations, and if so, we
then test each using `is_safe_powershell_command()` to determine if the
overall command is safe:


6e6338aa87/codex-rs/core/src/command_safety/windows_safe_commands.rs (L89-L98)

Unfortunately, our PowerShell parser did not recognize `@(...)` as a
special construct, so it was treated as an ordinary token. This meant
that the following would erroneously be considered "safe:"

```powershell
ls @(calc.exe)
```

The fix introduced in this PR is to do something comparable what we do
for Bash/Zsh, which is to use a "proper" parser to derive the list of
`execvp(3)` calls. For Bash/Zsh, we rely on
https://crates.io/crates/tree-sitter-bash, but there does not appear to
be a crate of comparable quality for parsing PowerShell statically
(https://github.com/airbus-cert/tree-sitter-powershell/ is the best
thing I found).

Instead, in this PR, we use a PowerShell script to parse the input
PowerShell program to produce the AST.
2025-12-12 13:06:49 -08:00
Dylan Hurd
26d0d822a2 chore(prompt) Update base prompt (#7943)
## Summary
Update base prompt
2025-12-12 20:50:49 +00:00
iceweasel-oai
677732ff65 Elevated Sandbox 4 (#7889) 2025-12-12 12:30:38 -08:00
Dylan Hurd
570eb5fe78 chore(prompt) Remove truncation details (#7941)
Fixes #7867 and #7906

## Summary
Update truncation details.
2025-12-12 20:21:53 +00:00
jif-oai
92098d36e8 feat: clean config loading and config api (#7924)
Check the README of the `config_loader` for details
2025-12-12 12:01:24 -08:00
Ahmed Ibrahim
149696d959 chores: models manager (#7937) 2025-12-12 18:59:39 +00:00
pakrym-oai
b3ddd50eee Remote compact for API-key users (#7835) 2025-12-12 10:05:02 -08:00
Dylan Hurd
9429e8b219 chore(gpt-5.2) prompt update (#7934)
## Summary
Updates
2025-12-12 17:50:09 +00:00
jif-oai
f152b16ed9 fix: race on rx subscription (#7921)
Fix race where the PTY was sending first chunk before the subscription
to the broadcast
2025-12-12 12:40:54 +01:00
jif-oai
b99ce883fe fix: break tui (#7876)
Prevent TUI to loop for ever if one of the RX it's listing on get closed
2025-12-12 11:50:50 +01:00
jif-oai
49bf49c2fa feat: more safe commands (#7728) 2025-12-12 11:48:25 +01:00
Victor Vannara
9287be762e fix(tui): show xhigh reasoning warning for gpt-5.2 (#7910)
## Notes
- Extend reasoning-effort popup warning eligibility to gpt-5.2* models
for the Extra High (xhigh) option.

## Revisions
- R2: Remove unnecessary tests and snapshots
- R1: initial

## Testing
- `just fix`, `cargo test -p codex-tui`, and `cargo test -p codex-tui2`
- Manual testing

**Before**:
<img width="864" height="162" alt="image"
src="https://github.com/user-attachments/assets/d12a8f11-3ba5-4c31-9ae9-096a408b4971"
/>

**After** (consistent with GPT 5.1 Codex Max):
<img width="864" height="156" alt="image"
src="https://github.com/user-attachments/assets/29c0ea7a-c68e-4fac-b10f-15a420ae5953"
/>

<img width="684" height="154" alt="image"
src="https://github.com/user-attachments/assets/b562b8b6-6e63-4dc2-8344-5c7f9a9b6263"
/>
2025-12-12 06:30:56 +00:00
Eric Traut
60479a9674 Make skill name and description limit based on characters not byte counts (#7915)
This PR changes the length validation for SKILL.md `name` and
`description` fields so they use character counts rather than byte
counts. Aligned character limits to other harnesses.

This addresses #7730.
2025-12-11 22:28:59 -08:00
Michael Bolin
4312cae005 feat: introduce utilities for locating pwsh.exe and powershell.exe (#7893)
I am trying to tighten up some of our logic around PowerShell over in
https://github.com/openai/codex/pull/7607 and it would be helpful to be
more precise about `pwsh.exe` versus `powershell.exe`, as they do not
accept the exact same input language.

To that end, this PR introduces utilities for detecting each on the
system. I think we also want to update `get_user_shell_path()` to return
PowerShell instead of `None` on Windows, but we'll consider that in a
separate PR since it may require more testing.
2025-12-11 22:07:54 -08:00
Victor Vannara
190fa9e104 docs: clarify xhigh reasoning effort on gpt-5.2 (#7911)
## Changes
- Update config docs and example config comments to state that "xhigh"
is supported on gpt-5.2 as well as gpt-5.1-codex-max
- Adjust the FAQ model-support section to reflect broader xhigh
availability
2025-12-11 21:18:47 -08:00
Shijie Rao
163a7e317e feat: use latest disk value for mcp servers status (#7907)
### Summary
Instead of stale in memory config value for listing mcp server statuses,
we pull the latest disk value.
2025-12-11 18:56:55 -08:00
Dylan Hurd
9e91e49edb Revert "fix(apply-patch): preserve CRLF line endings on Windows" (#7903)
Reverts openai/codex#7515
2025-12-11 17:58:35 -08:00
Ahmed Ibrahim
c787e9d0c0 Make migration screen dynamic (#7896)
# External (non-OpenAI) Pull Request Requirements

Before opening this Pull Request, please read the dedicated
"Contributing" markdown file or your PR may be closed:
https://github.com/openai/codex/blob/main/docs/contributing.md

If your PR conforms to our contribution guidelines, replace this text
with a detailed and high quality description of your changes.

Include a link to a bug report or enhancement request.
2025-12-11 16:41:04 -08:00
Victor Vannara
95f7d37ec6 Fix misleading 'maximize' high effort description on xhigh models (#7874)
## Notes
- switch misleading High reasoning effort descriptions from "Maximizes
reasoning depth" to "Higher reasoning depth" across models with xhigh
reasoning. Affects GPT-5.1 Codex Max and Robin
- refresh model list fixtures and chatwidget snapshots to match new copy

## Revision
- R2: Change 'Higher' to 'Greater'
- R1: Initial

## Testing

<img width="583" height="142" alt="image"
src="https://github.com/user-attachments/assets/1ddd8971-7841-4cb3-b9ba-91095a7435d2"
/>

<img width="838" height="142" alt="image"
src="https://github.com/user-attachments/assets/79aaedbf-7624-4695-b822-93dea7d6a800"
/>
2025-12-11 16:38:52 -08:00
Eric Traut
43e6e75317 Added deprecation notice for "chat" wire_api (#7897)
This PR adds a deprecation notice that appears once per invocation of
codex (not per conversation) when a conversation is started using a
custom model provider configured with the "chat" wire_api. We have
[announced](https://github.com/openai/codex/discussions/7782) that this
feature is deprecated and will be removed in early Feb 2026, so we want
to notify users of this fact.

The deprecation notice was added in a way that works with the
non-interactive "codex exec", the TUI, and with the extension. Screen
shots of each are below.

<img width="1000" height="89" alt="image"
src="https://github.com/user-attachments/assets/72cc08bb-d158-4a89-b3c8-7a896abd016f"
/>

<img width="1000" height="38" alt="Screenshot 2025-12-11 at 2 22 29 PM"
src="https://github.com/user-attachments/assets/7b2128ca-9afc-48be-9ce1-2ce81bc00fcb"
/>

<img width="479" height="106" alt="Screenshot 2025-12-11 at 2 21 26 PM"
src="https://github.com/user-attachments/assets/858ec1cc-ebfc-4c99-b22b-63015154d752"
/>
2025-12-11 15:24:43 -08:00
dank-openai
36610d975a Fix toasts on Windows under WSL 2 (#7137)
Before this: no notifications or toasts when using Codex CLI in WSL 2.

After this: I get toasts from Codex
2025-12-11 15:09:00 -08:00
Michael Bolin
e0d7ac51d3 fix: policy/*.codexpolicy -> rules/*.rules (#7888)
We decided that `*.rules` is a more fitting (and concise) file extension
than `*.codexpolicy`, so we are changing the file extension for the
"execpolicy" effort. We are also changing the subfolder of `$CODEX_HOME`
from `policy` to `rules` to match.

This PR updates the in-repo docs and we will update the public docs once
the next CLI release goes out.

Locally, I created `~/.codex/rules/default.rules` with the following
contents:

```
prefix_rule(pattern=["gh", "pr", "view"])
```

And then I asked Codex to run:

```
gh pr view 7888 --json title,body,comments
```

and it was able to!
2025-12-11 14:46:00 -08:00
Jeremy Rose
bacbe871c8 Update RMCP client config guidance (#7895)
## Summary
- update CLI OAuth guidance to reference `features.rmcp_client` instead
of the deprecated experimental flag
- keep login/logout help text consistent with the new feature flag

## Testing
- `cargo test -p codex-cli`


------
[Codex
Task](https://chatgpt.com/codex/tasks/task_i_693b3e0bf27c832cb66d585847a552ab)
2025-12-11 14:43:55 -08:00
Ahmed Ibrahim
b7fa7ca8e9 Update Model Info (#7853) 2025-12-11 14:06:07 -08:00
iceweasel-oai
3e81ed4b91 Elevated Sandbox 3 (#7809)
dedicated sandbox command runner exe.
2025-12-11 13:51:27 -08:00
Ahmed Ibrahim
c4f3f566a5 remove release script (#7885) 2025-12-11 13:40:48 -08:00
Ahmed Ibrahim
b9fb3b81e5 Chore: limit find family visability (#7891)
a little bit more code quality of life
2025-12-11 13:30:56 -08:00
Anton Panasenko
0af7e4a195 fix: omit reasoning summary when ReasoningSummary::None (#7845)
```
{
  "error": {
    "message": "Invalid value: 'none'. Supported values are: 'concise', 'detailed', and 'auto'.",
    "type": "invalid_request_error",
    "param": "reasoning.summary",
    "code": "invalid_value"
  }
}
```
2025-12-11 11:59:40 -08:00
Tyler Anton
8c4c6a19e0 fix: drop stale filedescriptor output hash for nix (#7865)
Fixes: #7863 

- Remove the `filedescriptor-0.8.3` entry from `codex-rs/default.nix`
output hashes because the crate now comes from crates.io.
2025-12-11 10:43:50 -08:00
sayan-oai
703bf12b36 fix: dont quit on 'q' in onboarding ApiKeyEntry state (#7869)
### What

Don't treat `q` as a special quit character on the API key paste page in
the onboarding flow.

This addresses #7413, where pasting API keys with `q` would cause codex
to quit on Windows.

### Test Plan

Tested on Windows and MacOS.
2025-12-11 09:57:59 -08:00
pakrym-oai
bb8fdb20dc Revert "Only show Worked for after the final assistant message" (#7884)
Reverts openai/codex#7854
2025-12-11 09:11:42 -08:00
Ahmed Ibrahim
238ce7dfad feat: robin (#7882)
<img width="554" height="554" alt="image"
src="https://github.com/user-attachments/assets/aa86f4c8-fb34-4b0e-8b03-3a9980dfdb08"
/>

---------

Co-authored-by: Dylan Hurd <dylan.hurd@openai.com>
2025-12-11 09:04:08 -08:00
jif-oai
d4554ce6c8 fix: flaky tests 4 (#7875) 2025-12-11 14:26:27 +00:00
jif-oai
29381ba5c2 feat: add shell snapshot for shell command (#7786) 2025-12-11 13:46:43 +00:00
jif-oai
b2280d6205 feat: warning for long snapshots (#7870) 2025-12-11 12:42:47 +00:00
Dylan Hurd
dca7f4cb60 fix(stuff) (#7855)
Co-authored-by: Ahmed Ibrahim <aibrahim@openai.com>
2025-12-11 00:39:47 -08:00
iceweasel-oai
13c0919bff Elevated Sandbox 2 (#7792)
- DPAPI helpers for storing Sandbox user passwords securely
- creation of Offline/Online sandbox users
- ACL setup for sandbox users
- firewall rule setup
2025-12-10 21:23:16 -08:00
pakrym-oai
83aac0f985 Only show Worked for after the final assistant message (#7854)
Before:
<img width="1908" height="246" alt="image"
src="https://github.com/user-attachments/assets/f4d5993a-8d37-4982-a6fd-d37f449215b2"
/>
After:
<img width="1102" height="586" alt="image"
src="https://github.com/user-attachments/assets/e833140d-690a-4c33-8bc7-e2b69b9dc92d"
/>
2025-12-10 21:13:13 -08:00
Eric Traut
057250020a Fixed regression that broke fuzzy matching for slash commands (#7859)
This addresses bug #7857 which was introduced recently as part of PR
#7704.
2025-12-10 20:42:45 -08:00
Michael Bolin
3fc8b2894f fix: remove inaccurate #[allow(dead_code)] marker (#7851)
Me reading this clippy warning:

<img width="263" height="191" alt="image"
src="https://github.com/user-attachments/assets/3a936a17-f91d-47bc-a08a-cafb154e9e32"
/>
2025-12-10 17:48:46 -08:00
Celia Chen
ce19dbbb22 [app-server] Update readme to include mcp endpoints (#7850)
n/a
2025-12-11 01:08:31 +00:00
Michael Bolin
038767af69 fix: add a hopefully-temporary sleep to reduce test flakiness (#7848)
Let's see if this `sleep()` call is good enough to fix the test
flakiness we currently see in CI. It will take me some time to upstream
a proper fix, and I would prefer not to disable this test in the
interim.
2025-12-11 00:51:33 +00:00
Celia Chen
7cabe54fc7 [app-server] make app server not throw error when login id is not found (#7831)
Our previous design of cancellation endpoint is not idempotent, which
caused a bunch of flaky tests. Make app server just returned a not_found
status instead of throwing an error if the login id is not found. Keep
V1 endpoint behavior the same.
2025-12-10 16:19:40 -08:00
zhao-oai
c1367808fb fixing typo in execpolicy docs (#7847) 2025-12-10 16:11:46 -08:00
Michael Bolin
87f5b69b24 fix: ensure accept_elicitation_for_prompt_rule() test passes locally (#7832)
When I originally introduced `accept_elicitation_for_prompt_rule()` in
https://github.com/openai/codex/pull/7617, it worked for me locally
because I had run `codex-rs/exec-server/tests/suite/bash` once myself,
which had the side-effect of installing the corresponding DotSlash
artifact.

In CI, I added explicit logic to do this as part of
`.github/workflows/rust-ci.yml`, which meant the test also passed in CI,
but this logic should have been done as part of the test so that it
would work locally for devs who had not installed the DotSlash artifact
for `codex-rs/exec-server/tests/suite/bash` before. This PR updates the
test to do this (and deletes the setup logic from `rust-ci.yml`),
creating a new `DOTSLASH_CACHE` in a temp directory so that this is
handled independently for each test.

While here, also added a check to ensure that the `codex` binary has
been built prior to running the test, as we have to ensure it is
symlinked as `codex-linux-sandbox` on Linux in order for the integration
test to work on that platform.
2025-12-10 15:17:13 -08:00
Javi
e2559ab28d fix: thread/list returning fewer than the requested amount due to filtering CXA-293 (#7509)
This caused some conversations to not appear when they otherwise should.

Prior to this change, `thread/list`/`list_conversations_common` would:
- Fetch N conversations from `RolloutRecorder::list_conversations`
- Then it would filter those (like by the provided `model_providers`)
- This would make it potentially return less than N items.

With this change:
- `list_conversations_common` now continues fetching more conversations
from `RolloutRecorder::list_conversations` until it "fills up" the
`requested_page_size`.
- Ultimately this means that clients can rely on getting eg 20
conversations if they request 20 conversations.
2025-12-10 23:06:32 +00:00
Josh McKinney
90f262e9a4 feat(tui2): copy tui crate and normalize snapshots (#7833)
Introduce a full codex-tui source snapshot under the new codex-tui2
crate so viewport work can be replayed in isolation.

This change copies the entire codex-rs/tui/src tree into
codex-rs/tui2/src in one atomic step, rather than piecemeal, to keep
future diffs vs the original viewport bookmark easy to reason about.

The goal is for codex-tui2 to render identically to the existing TUI
behind the `features.tui2` flag while we gradually port the
viewport/history commits from the joshka/viewport bookmark onto this
forked tree.

While on this baseline change, we also ran the codex-tui2 snapshot test
suite and accepted all insta snapshots for the new crate, so the
snapshot files now use the codex-tui2 naming scheme and encode the
unmodified legacy TUI behavior. This keeps later viewport commits
focused on intentional behavior changes (and their snapshots) rather
than on mechanical snapshot renames.
2025-12-10 22:53:46 +00:00
Ahmed Ibrahim
321625072a Show the default model in model picker (#7838)
See the snapshot
2025-12-10 14:01:18 -08:00
xl-openai
b36ecb6c32 Inject SKILL.md when it's explicitly mentioned. (#7763)
1. Skills load once in core at session start; the cached outcome is
reused across core and surfaced to TUI via SessionConfigured.
2. TUI detects explicit skill selections, and core injects the matching
SKILL.md content into the turn when a selected skill is present.
2025-12-10 13:59:17 -08:00
pakrym-oai
eb2e5458cc Disable ansi codes in tui log file (#7836) 2025-12-10 13:56:48 -08:00
Celia Chen
bfb4d5710b [app-server-protocol] Add types for config (#7658)
Currently the config returned by `config/read` in untyped. Add types so
it's easier for client to parse the config. Since currently configs are
all defined in snake case we'll keep that instead of using camel case
like the rest of V2.

Sample output by testing using the app server test client:
```
{
<   "id": "f28449f4-b015-459b-b07b-eef06980165d",
<   "result": {
<     "config": {
<       "approvalPolicy": null,
<       "compactPrompt": null,
<       "developerInstructions": null,
<       "features": {
<         "experimental_use_rmcp_client": true
<       },
<       "forcedChatgptWorkspaceId": null,
<       "forcedLoginMethod": null,
<       "instructions": null,
<       "model": "gpt-5.1-codex-max",
<       "modelAutoCompactTokenLimit": null,
<       "modelContextWindow": null,
<       "modelProvider": null,
<       "modelReasoningEffort": null,
<       "modelReasoningSummary": null,
<       "modelVerbosity": null,
<       "model_providers": {
<         "local": {
<           "base_url": "http://localhost:8061/api/codex",
<           "env_http_headers": {
<             "ChatGPT-Account-ID": "OPENAI_ACCOUNT_ID"
<           },
<           "env_key": "CHATGPT_TOKEN_STAGING",
<           "name": "local",
<           "wire_api": "responses"
<         }
<       },
<       "model_reasoning_effort": "medium",
<       "notice": {
<         "hide_gpt-5.1-codex-max_migration_prompt": true,
<         "hide_gpt5_1_migration_prompt": true
<       },
<       "profile": null,
<       "profiles": {},
<       "projects": {
<         "/Users/celia/code": {
<           "trust_level": "trusted"
<         },
<         "/Users/celia/code/codex": {
<           "trust_level": "trusted"
<         },
<         "/Users/celia/code/openai": {
<           "trust_level": "trusted"
<         }
<       },
<       "reviewModel": null,
<       "sandboxMode": null,
<       "sandboxWorkspaceWrite": null,
<       "tools": {
<         "viewImage": null,
<         "webSearch": null
<       }
<     },
<     "origins": {
<       "features.experimental_use_rmcp_client": {
<         "name": "user",
<         "source": "/Users/celia/.codex/config.toml",
<         "version": "sha256:a1d8eaedb5d9db5dfdfa69f30fa9df2efec66bb4dd46aa67f149fcc67cd0711c"
<       },
<       "model": {
<         "name": "user",
<         "source": "/Users/celia/.codex/config.toml",
<         "version": "sha256:a1d8eaedb5d9db5dfdfa69f30fa9df2efec66bb4dd46aa67f149fcc67cd0711c"
<       },
<       "model_providers.local.base_url": {
<         "name": "user",
<         "source": "/Users/celia/.codex/config.toml",
<         "version": "sha256:a1d8eaedb5d9db5dfdfa69f30fa9df2efec66bb4dd46aa67f149fcc67cd0711c"
<       },
<       "model_providers.local.env_http_headers.ChatGPT-Account-ID": {
<         "name": "user",
<         "source": "/Users/celia/.codex/config.toml",
<         "version": "sha256:a1d8eaedb5d9db5dfdfa69f30fa9df2efec66bb4dd46aa67f149fcc67cd0711c"
<       },
<       "model_providers.local.env_key": {
<         "name": "user",
<         "source": "/Users/celia/.codex/config.toml",
<         "version": "sha256:a1d8eaedb5d9db5dfdfa69f30fa9df2efec66bb4dd46aa67f149fcc67cd0711c"
<       },
<       "model_providers.local.name": {
<         "name": "user",
<         "source": "/Users/celia/.codex/config.toml",
<         "version": "sha256:a1d8eaedb5d9db5dfdfa69f30fa9df2efec66bb4dd46aa67f149fcc67cd0711c"
<       },
<       "model_providers.local.wire_api": {
<         "name": "user",
<         "source": "/Users/celia/.codex/config.toml",
<         "version": "sha256:a1d8eaedb5d9db5dfdfa69f30fa9df2efec66bb4dd46aa67f149fcc67cd0711c"
<       },
<       "model_reasoning_effort": {
<         "name": "user",
<         "source": "/Users/celia/.codex/config.toml",
<         "version": "sha256:a1d8eaedb5d9db5dfdfa69f30fa9df2efec66bb4dd46aa67f149fcc67cd0711c"
<       },
<       "notice.hide_gpt-5.1-codex-max_migration_prompt": {
<         "name": "user",
<         "source": "/Users/celia/.codex/config.toml",
<         "version": "sha256:a1d8eaedb5d9db5dfdfa69f30fa9df2efec66bb4dd46aa67f149fcc67cd0711c"
<       },
<       "notice.hide_gpt5_1_migration_prompt": {
<         "name": "user",
<         "source": "/Users/celia/.codex/config.toml",
<         "version": "sha256:a1d8eaedb5d9db5dfdfa69f30fa9df2efec66bb4dd46aa67f149fcc67cd0711c"
<       },
<       "projects./Users/celia/code.trust_level": {
<         "name": "user",
<         "source": "/Users/celia/.codex/config.toml",
<         "version": "sha256:a1d8eaedb5d9db5dfdfa69f30fa9df2efec66bb4dd46aa67f149fcc67cd0711c"
<       },
<       "projects./Users/celia/code/codex.trust_level": {
<         "name": "user",
<         "source": "/Users/celia/.codex/config.toml",
<         "version": "sha256:a1d8eaedb5d9db5dfdfa69f30fa9df2efec66bb4dd46aa67f149fcc67cd0711c"
<       },
<       "projects./Users/celia/code/openai.trust_level": {
<         "name": "user",
<         "source": "/Users/celia/.codex/config.toml",
<         "version": "sha256:a1d8eaedb5d9db5dfdfa69f30fa9df2efec66bb4dd46aa67f149fcc67cd0711c"
<       },
<       "tools.web_search": {
<         "name": "user",
<         "source": "/Users/celia/.codex/config.toml",
<         "version": "sha256:a1d8eaedb5d9db5dfdfa69f30fa9df2efec66bb4dd46aa67f149fcc67cd0711c"
<       }
<     }
<   }
< }
```
2025-12-10 21:35:31 +00:00
Ahmed Ibrahim
4953b2ae09 Error when trying to push a release while another release is in progress (#7834)
<img width="995" height="171" alt="image"
src="https://github.com/user-attachments/assets/7bab541a-a933-4064-a968-26e9566360ec"
/>

Currently, we just cancel the in progress release which can be annoying
2025-12-10 12:15:39 -08:00
Robby He
1a5809624d fix: Prevent slash command popup from activating on invalid inputs (#7704)
## Slash Command popup issue

#7659

When recalling history, the
composer(`codex_tui::bottom_pane::chat_composer`) restores the previous
prompt text (which may start with `/`) and then calls
`sync_command_popup`. The logic in `sync_command_popup` treats any first
line that starts with `/` and has the caret inside the initial `/name`
token as an active slash command name:

```rust
let is_editing_slash_command_name = if first_line.starts_with('/') && caret_on_first_line {
    let token_end = first_line
        .char_indices()
        .find(|(_, c)| c.is_whitespace())
        .map(|(i, _)| i)
        .unwrap_or(first_line.len());
    cursor <= token_end
} else {
    false
};
```

This detection does not distinguish between an actual interactive slash
command being typed and a normal historical prompt that happens to begin
with `/`. As a result, after history recall, the restored prompt like `/
test` is interpreted as an "editing command name" context and the
slash-command popup is (re)activated. Once `active_popup` is
`ActivePopup::Command`, subsequent `Up` key presses are handled by
`handle_key_event_with_slash_popup` instead of
`handle_key_event_without_popup`, so they no longer trigger
`history.navigate_up(...)` and the session prompt history cannot be
scrolled.
2025-12-10 11:38:15 -08:00
Ahmed Ibrahim
cb9a189857 make model optional in config (#7769)
- Make Config.model optional and centralize default-selection logic in
ModelsManager, including a default_model helper (with
codex-auto-balanced when available) so sessions now carry an explicit
chosen model separate from the base config.
- Resolve `model` once in `core` and `tui` from config. Then store the
state of it on other structs.
- Move refreshing models to be before resolving the default model
2025-12-10 11:19:00 -08:00
Celia Chen
8a71f8b634 [app-server] Make sure that config writes preserve comments & order or configs (#7789)
Make sure that config writes preserve comments and order of configs by
utilizing the ConfigEditsBuilder in core.

Tested by running a real example and made sure that nothing in the
config file changes other than the configs to edit.
2025-12-10 19:14:27 +00:00
pakrym-oai
4b684c53ae Remove conversation_id and bring back request ID logging (#7830) 2025-12-10 10:44:12 -08:00
Koichi Shiraishi
9f40d6eeeb fix: remove duplicated parallel FeatureSpec (#7823)
regression: #7589

Signed-off-by: Koichi Shiraishi <zchee.io@gmail.com>
2025-12-10 10:23:01 -08:00
Amit Halfon
bd51d1b103 fix: Upgrade @modelcontextprotocol/sdk to ^1.24.0 (#7817)
## What?
Upgrades @modelcontextprotocol/sdk from ^1.20.2 to ^1.24.0 in the
TypeScript SDK's devDependencies.

## Why?
Related to #7737 - keeping development dependencies up to date with the
latest MCP SDK version that includes the fix for CVE-2025-66414.

Note: This change does not address the CVE for Codex users, as the MCP
SDK is only in devDependencies here. The actual MCP integration that
would be affected by the CVE is in the Rust codebase.

## How?
•  Updated dependency version in sdk/typescript/package.json
•  Ran pnpm install to update lockfile
•  Fixed formatting (added missing newline in package.json)

## Related Issue
Related to #7737

## Test Status
⚠️ After this upgrade, 2 additional tests timeout (1 test was already
failing on main):
•  tests/run.test.ts: "sends previous items when run is called twice" 
•  tests/run.test.ts: "resumes thread by id"
• tests/runStreamed.test.ts: "sends previous items when runStreamed is
called twice"

Marking as draft to investigate test timeouts. Maintainer guidance would
be appreciated.

Co-authored-by: HalfonA <amit@miggo.io>
2025-12-10 10:17:00 -08:00
jif-oai
f677d05871 fix: flaky tests 3 (#7826) 2025-12-10 17:57:53 +00:00
Eric Traut
c4af707e09 Removed experimental "command risk assessment" feature (#7799)
This experimental feature received lukewarm reception during internal
testing. Removing from the code base.
2025-12-10 09:48:11 -08:00
zhao-oai
e0fb3ca1db refactoring with_escalated_permissions to use SandboxPermissions instead (#7750)
helpful in the future if we want more granularity for requesting
escalated permissions:
e.g when running in readonly sandbox, model can request to escalate to a
sandbox that allows writes
2025-12-10 17:18:48 +00:00
jif-oai
97b90094cd feat: use remote branch for review is local trails (#7813) 2025-12-10 17:04:52 +00:00
jif-oai
463249eff3 fix: flaky test 2 (#7818) 2025-12-10 16:35:28 +00:00
jif-oai
0ad54982ae chore: rework unified exec events (#7775) 2025-12-10 10:30:38 +00:00
Shijie Rao
d1c5db5796 chore: disable trusted signing pkg cache hit (#7807) 2025-12-09 22:14:14 -08:00
Gav Verma
6fa24d65f5 Express rate limit warning as % remaining (#7795)
<img width="342" height="264" alt="image"
src="https://github.com/user-attachments/assets/f1e932ff-c550-47b3-9035-0299ada4998d"
/>

Earlier, the warning was expressed as consumed% whereas status was
expressed as remaining%. This change brings the two into sync to
minimize confusion and improve visual consistency.
2025-12-09 21:17:57 -08:00
Shijie Rao
ab9ddcd50b Revert "Revert "feat: windows codesign with Azure trusted signing"" (#7806)
Reverts openai/codex#7804
2025-12-09 20:42:00 -08:00
Shijie Rao
f11520f5f1 Revert "feat: windows codesign with Azure trusted signing" (#7804)
Reverts openai/codex#7757
2025-12-09 20:19:37 -08:00
Shijie Rao
42e0817398 Revert "Revert "feat: windows codesign with Azure trusted signing"" (#7757)
Reverts openai/codex#7753

Updated the tag ref matching at
https://github.com/openai/openai/pull/594858 so that release with tag
change can be picked up correctly.
2025-12-09 19:31:46 -08:00
iceweasel-oai
fc4249313b Elevated Sandbox 1 (#7788)
- updating helpers, refactoring some functions that will be used in the
elevated sandbox
- better logging
- better and faster handling of ACL checks/writes
- No functional change—legacy restricted-token sandbox
remains the only path.
2025-12-09 19:00:33 -08:00
pakrym-oai
967d063f4b parse rg | head a search (#7797) 2025-12-09 18:30:16 -08:00
Shijie Rao
893f5261eb feat: support mcp in-session login (#7751)
### Summary
* Added `mcpServer/oauthLogin` in app server for supporting in session
MCP server login
* Added `McpServerOauthLoginParams` and `McpServerOauthLoginResponse` to
support above method with response returning the auth URL for consumer
to open browser or display accordingly.
* Added `McpServerOauthLoginCompletedNotification` which the app server
would emit on MCP server login success or failure (i.e. timeout).
* Refactored rmcp-client oath_login to have the ability on starting a
auth server which the codex_message_processor uses for in-session auth.
2025-12-09 17:43:53 -08:00
Michael Bolin
fa4cac1e6b fix: introduce AbsolutePathBuf and resolve relative paths in config.toml (#7796)
This PR attempts to solve two problems by introducing a
`AbsolutePathBuf` type with a special deserializer:

- `AbsolutePathBuf` attempts to be a generally useful abstraction, as it
ensures, by constructing, that it represents a value that is an
absolute, normalized path, which is a stronger guarantee than an
arbitrary `PathBuf`.
- Values in `config.toml` that can be either an absolute or relative
path should be resolved against the folder containing the `config.toml`
in the relative path case. This PR makes this easy to support: the main
cost is ensuring `AbsolutePathBufGuard` is used inside
`deserialize_config_toml_with_base()`.

While `AbsolutePathBufGuard` may seem slightly distasteful because it
relies on thread-local storage, this seems much cleaner to me than using
than my various experiments with
https://docs.rs/serde/latest/serde/de/trait.DeserializeSeed.html.
Further, since the `deserialize()` method from the `Deserialize` trait
is not async, we do not really have to worry about the deserialization
work being spread across multiple threads in a way that would interfere
with `AbsolutePathBufGuard`.

To start, this PR introduces the use of `AbsolutePathBuf` in
`OtelTlsConfig`. Note how this simplifies `otel_provider.rs` because it
no longer requires `settings.codex_home` to be threaded through.
Furthermore, this sets us up better for a world where multiple
`config.toml` files from different folders could be loaded and then
merged together, as the absolutifying of the paths must be done against
the correct parent folder.
2025-12-09 17:37:52 -08:00
Josh McKinney
0c8828c5e2 feat(tui2): add feature-flagged tui2 frontend (#7793)
Introduce a new codex-tui2 crate that re-exports the existing
interactive TUI surface and delegates run_main directly to codex-tui.
This keeps behavior identical while giving tui2 its own crate for future
viewport work.

Wire the codex CLI to select the frontend via the tui2 feature flag.
When the merged CLI overrides include features.tui2=true (e.g. via
--enable tui2), interactive runs are routed through
codex_tui2::run_main; otherwise they continue to use the original
codex_tui::run_main.

Register Feature::Tui2 in the core feature registry and add the tui2
crate and dependency entries so the new frontend builds alongside the
existing TUI.

This is a stub that only wires up the feature flag for this.

<img width="619" height="364" alt="image"
src="https://github.com/user-attachments/assets/4893f030-932f-471e-a443-63fe6b5d8ed9"
/>
2025-12-09 16:23:53 -08:00
Bryant Rolfe
225a5f7ffb Add vim-style navigation for CLI option selection (#7784)
## Summary

Support "j" and "k" keys as aliases for "down" and "up" so vim users
feel loved. Only support these keys when the selection is not
searchable.

## Testing
- env -u NO_COLOR TERM=xterm-256color cargo test -p codex-tui


------
[Codex
Task](https://chatgpt.com/codex/tasks/task_i_693771b53bc8833088669060dfac2083)
2025-12-09 22:41:10 +00:00
zhao-oai
05e546ee1f fix more typos in execpolicy.md (#7787) 2025-12-09 13:23:14 -08:00
jif-oai
7836aeddae feat: shell snapshotting (#7641) 2025-12-09 18:36:58 +00:00
Job Chong
ac3237721e Fix: gracefully error out for unsupported images (#7478)
Fix for #7459 
## What
Since codex errors out for unsupported images, stop attempting to
base64/attach them and instead emit a clear placeholder when the file
isn’t a supported image MIME.

## Why
Local uploads for unsupported formats (e.g., SVG/GIF/etc.) were
dead-ending after decode failures because of the 400 retry loop. Users
now get an explicit “cannot attach … unsupported image format …”
response.

## How
Replace the fallback read/encode path with MIME detection that bails out
for non-image or unsupported image types, returning a consistent
placeholder. Unreadable and invalid images still produce their existing
error placeholders.
2025-12-09 10:28:41 -08:00
Josh McKinney
9df70a0772 Add vim navigation keys to transcript pager (#7550)
## Summary
- add vim-style pager navigation for transcript overlays (j/k,
ctrl+f/b/d/u) without removing existing keys
- add shift-space to page up

------
[Codex
Task](https://chatgpt.com/codex/tasks/task_i_69309d26da508329908b2dc8ca40afb7)
2025-12-09 10:23:11 -08:00
Michael Bolin
a7e3e37da8 fix: allow sendmsg(2) and recvmsg(2) syscalls in our Linux sandbox (#7779)
This changes our default Landlock policy to allow `sendmsg(2)` and
`recvmsg(2)` syscalls. We believe these were originally denied out of an
abundance of caution, but given that `send(2)` nor `recv(2)` are allowed
today [which provide comparable capability to the `*msg` equivalents],
we do not believe allowing them grants any privileges beyond what we
already allow.

Rather than using the syscall as the security boundary, preventing
access to the potentially hazardous file descriptor in the first place
seems like the right layer of defense.

In particular, this makes it possible for `shell-tool-mcp` to run on
Linux when using a read-only sandbox for the Bash process, as
demonstrated by `accept_elicitation_for_prompt_rule()` now succeeding in
CI.
2025-12-09 09:24:01 -08:00
pakrym-oai
164265bed1 Vendor ConPtySystem (#7656)
The repo we were depending on is very large and we need very small part
of it.

---------

Co-authored-by: Pavel <pavel@krymets.com>
2025-12-09 17:23:51 +00:00
Tyler Anton
2237b701b6 Fix Nix cargo output hashes for rmcp and filedescriptor (#7762)
Fixes #7759:

- Drop the stale `rmcp` entry from `codex-rs/default.nix`’s
`cargoLock.outputHashes` since the crate now comes from crates.io and no
longer needs a git hash.
- Add the missing hash for the filedescriptor-0.8.3 git dependency (from
`pakrym/wezterm`) so `buildRustPackage` can vendor it.
2025-12-09 09:04:36 -08:00
jif-oai
6382dc2338 chore: enable parallel tc (#7589) 2025-12-09 17:00:56 +00:00
cassirer-openai
80140c6d9d Use codex-max prompt/tools for experimental models. (#7765) 2025-12-09 07:56:23 +00:00
muyuanjin
933e247e9f Fix transcript pager page continuity (#7363)
## What

Fix PageUp/PageDown behaviour in the Ctrl+T transcript overlay so that
paging is continuous and reversible, and add tests to lock in the
expected behaviour.

## Why

Today, paging in the transcript overlay uses the raw viewport height
instead of the effective content height after layout. Because the
overlay reserves some rows for chrome (header/footer), this can cause:

- PageDown to skip transcript lines between pages.
- PageUp/PageDown not to “round-trip” cleanly (PageDown then PageUp does
not always return to the same set of visible lines).

This shows up when inspecting longer transcripts via Ctrl+T; see #7356
for context.

## How

- Add a dedicated `PagerView::page_step` helper that computes the page
size from the last rendered content height and falls back to
`content_area(viewport_area).height` when that is not yet available.
- Use `page_step(...)` for both PageUp and PageDown (including SPACE) so
the scroll step always matches the actual content area height, not the
full viewport height.
- Add a focused test
`transcript_overlay_paging_is_continuous_and_round_trips` that:
  - Renders a synthetic transcript with numbered `line-NN` rows.
- Asserts that successive PageDown operations show continuous line
numbers (no gaps).
- Asserts that PageDown+PageUp and PageUp+PageDown round-trip correctly
from non-edge offsets.

The change is limited to `codex-rs/tui/src/pager_overlay.rs` and only
affects the transcript overlay paging semantics.

## Related issue

- #7356

## Testing

On Windows 11, using PowerShell 7 in the repo root:

```powershell
cargo test
cargo clippy --tests
cargo fmt -- --config imports_granularity=Item
```

- All tests passed.
- `cargo clippy --tests` reported some pre-existing warnings that are
unrelated to this change; no new lints were introduced in the modified
code.

---------

Signed-off-by: muyuanjin <24222808+muyuanjin@users.noreply.github.com>
Co-authored-by: Eric Traut <etraut@openai.com>
2025-12-08 18:45:20 -08:00
Ahmed Ibrahim
68505abf0f use chatgpt provider for /models (#7756)
This endpoint only exist on chatgpt
2025-12-08 17:42:24 -08:00
Ahmed Ibrahim
cacfd003ac override instructions using ModelInfo (#7754)
Making sure we can override base instructions
2025-12-08 17:30:42 -08:00
Shijie Rao
0f2b589d5e Revert "feat: windows codesign with Azure trusted signing" (#7753)
Reverts openai/codex#7675
2025-12-08 16:09:28 -08:00
Michael Bolin
06704b1a0f fix: pre-main hardening logic must tolerate non-UTF-8 env vars (#7749)
We received a bug report that Codex CLI crashes when an env var contains
a non-ASCII character, or more specifically, cannot be decoded as UTF-8:

```shell
$ RUST_BACKTRACE=full RÖDBURK=1 codex

thread '<unnamed>' panicked at library/std/src/env.rs:162:57:
called `Result::unwrap()` on an `Err` value: "RÃ\xB6DBURK"
stack backtrace:
   0:        0x101905c18 - __mh_execute_header
   1:        0x1012bd76c - __mh_execute_header
   2:        0x1019050e4 - __mh_execute_header
   3:        0x101905ad8 - __mh_execute_header
   4:        0x101905874 - __mh_execute_header
   5:        0x101904f38 - __mh_execute_header
   6:        0x1019347bc - __mh_execute_header
   7:        0x10193472c - __mh_execute_header
   8:        0x101937884 - __mh_execute_header
   9:        0x101b3bcd0 - __mh_execute_header
  10:        0x101b3c0bc - __mh_execute_header
  11:        0x101927a20 - __mh_execute_header
  12:        0x1005c58d8 - __mh_execute_header

thread '<unnamed>' panicked at library/core/src/panicking.rs:225:5:
panic in a function that cannot unwind
stack backtrace:
   0:        0x101905c18 - __mh_execute_header
   1:        0x1012bd76c - __mh_execute_header
   2:        0x1019050e4 - __mh_execute_header
   3:        0x101905ad8 - __mh_execute_header
   4:        0x101905874 - __mh_execute_header
   5:        0x101904f38 - __mh_execute_header
   6:        0x101934794 - __mh_execute_header
   7:        0x10193472c - __mh_execute_header
   8:        0x101937884 - __mh_execute_header
   9:        0x101b3c144 - __mh_execute_header
  10:        0x101b3c1a0 - __mh_execute_header
  11:        0x101b3c158 - __mh_execute_header
  12:        0x1005c5ef8 - __mh_execute_header
thread caused non-unwinding panic. aborting.
```

I discovered I could reproduce this on a release build, but not a dev
build, so between that and the unhelpful stack trace, my mind went to
the pre-`main()` logic we run in prod builds. Sure enough, we were
operating on `std::env::vars()` instead of `std::env::vars_os()`, which
is why the non-UTF-8 environment variable was causing an issue.

This PR updates the logic to use `std::env::vars_os()` and adds a unit
test.

And to be extra sure, I also verified the fix works with a local release
build:

```
$ cargo build --bin codex --release
$ RÖDBURK=1 ./target/release/codex --version
codex-cli 0.0.0
```
2025-12-08 16:00:24 -08:00
Ahmed Ibrahim
382f047a10 Remove legacy ModelInfo and merge it with ModelFamily (#7748)
This is a step towards removing the need to know `model` when
constructing config. We firstly don't need to know `model_info` and just
respect if the user has already set it. Next step, we don't need to know
`model` unless the user explicitly set it in `config.toml`
2025-12-08 15:29:37 -08:00
pakrym-oai
ac5fa6baf8 Do not emit start/end events for write stdin (#7561) 2025-12-08 15:23:02 -08:00
Shijie Rao
badda736c6 feat: windows codesign with Azure trusted signing (#7675)
### Summary
Set up codesign for windows dist with [Azure trusted
signing](https://azure.microsoft.com/en-us/products/trusted-signing) and
[its github action
integration](https://github.com/Azure/trusted-signing-action).
2025-12-08 15:12:01 -08:00
Ahmed Ibrahim
cb45139244 Add formatting client version to the x.x.x style. (#7711)
To avoid regression with special builds like alphas
2025-12-08 14:52:39 -08:00
Josh McKinney
a9f566af7b Restore status header after stream recovery (#7660)
## Summary
- restore the previous status header when a non-error event arrives
after a stream retry
- add a regression test to ensure the reconnect banner clears once
streaming resumes

## Testing
- cargo fmt -- --config imports_granularity=Item
- cargo clippy --fix --all-features --tests --allow-dirty -p codex-tui
- NO_COLOR=0 cargo test -p codex-tui *(fails: vt100 color assertion
tests expect colored cells but the environment returns Default colors
even with NO_COLOR cleared and TERM/COLORTERM set)*

------
[Codex
Task](https://chatgpt.com/codex/tasks/task_i_69337f8c77508329b3ea85134d4a7ac7)
2025-12-08 14:33:00 -08:00
Ahmed Ibrahim
71c75e648c Enhance model picker (#7709)
# External (non-OpenAI) Pull Request Requirements

Before opening this Pull Request, please read the dedicated
"Contributing" markdown file or your PR may be closed:
https://github.com/openai/codex/blob/main/docs/contributing.md

If your PR conforms to our contribution guidelines, replace this text
with a detailed and high quality description of your changes.

Include a link to a bug report or enhancement request.
2025-12-08 14:22:51 -08:00
zhao-oai
0a32acaa2d updating app server types to support execpoilcy amendment (#7747)
also includes minor refactor merging `ApprovalDecision` with
`CommandExecutionRequestAcceptSettings`
2025-12-08 13:56:22 -08:00
Ahmed Ibrahim
222a491570 load models from disk and set a ttl and etag (#7722)
# External (non-OpenAI) Pull Request Requirements

Before opening this Pull Request, please read the dedicated
"Contributing" markdown file or your PR may be closed:
https://github.com/openai/codex/blob/main/docs/contributing.md

If your PR conforms to our contribution guidelines, replace this text
with a detailed and high quality description of your changes.

Include a link to a bug report or enhancement request.
2025-12-08 13:43:04 -08:00
Takuto Yuki
4a3e9ed88d fix(tui): add missing Ctrl+n/Ctrl+p support to ListSelectionView (#7629)
## Summary

Extend Ctrl+n/Ctrl+p navigation support to selection popups (model
picker, approval mode, etc.)

This is a follow-up to #7530, which added Ctrl+n/Ctrl+p navigation to
the textarea.
The same keybindings were missing from `ListSelectionView`, causing
inconsistent behavior
  when navigating selection popups.

  ## Related

  - #7530 - feat(tui): map Ctrl-P/N to arrow navigation in textarea

  ## Changes

  - Added Ctrl+n as alternative to Down arrow in selection popups
  - Added Ctrl+p as alternative to Up arrow in selection popups
  - Added unit tests for the new keybindings

  ## Test Plan

  - [x] `cargo test -p codex-tui list_selection_view` - all tests pass
- [x] Manual testing: verified Ctrl+n/p navigation works in model
selection popup

---------

Co-authored-by: Eric Traut <etraut@openai.com>
2025-12-08 11:21:15 -08:00
Shijie Rao
28e7218c0b feat: linux codesign with sigstore (#7674)
### Summary
Linux codesigning with sigstore and test run output at
https://github.com/openai/codex/actions/runs/19994328162?pr=7662.

Sigstore is one of the few ways for codesigning for linux platform.
Linux is open sourced and therefore binary/dist validation comes with
the build itself instead of a central authority like Windows or Mac.
Alternative here is to use GPG which again a public key included with
the bundle for validation. Advantage with Sigstore is that we do not
have to create a private key for signing but rather with[ keyless
signing](https://docs.sigstore.dev/cosign/signing/overview/).

This should be sufficient for us at this point and if we want to we can
support GPG in the future.
2025-12-08 11:13:50 -08:00
Matthew Zeng
585f75bd5a Make the device auth instructions more clear. (#7745)
- [x] Make the device auth instructions more clear.
2025-12-08 11:04:49 -08:00
jif-oai
da983c1761 feat: add is-mutating detection for shell command handler (#7729) 2025-12-08 18:42:09 +00:00
zhao-oai
c2bdee0946 proposing execpolicy amendment when prompting due to sandbox denial (#7653)
Currently, we only show the “don’t ask again for commands that start
with…” option when a command is immediately flagged as needing approval.
However, there is another case where we ask for approval: When a command
is initially auto-approved to run within sandbox, but it fails to run
inside sandbox, we would like to attempt to retry running outside of
sandbox. This will require a prompt to the user.

This PR addresses this latter case
2025-12-08 17:55:20 +00:00
zhao-oai
cfda44b98b fix wrap behavior for long commands (#7655)
before:
<img width="720" height="225" alt="image"
src="https://github.com/user-attachments/assets/19b7ad7c-db14-4792-97cc-80677a3a52ec"
/>
after:
<img width="500" height="219" alt="Screenshot 2025-12-05 at 4 37 14 PM"
src="https://github.com/user-attachments/assets/f877f846-5943-4ca7-8949-89e8524ffdb9"
/>


also removes `is_current`, which is deadcode
2025-12-08 17:35:03 +00:00
dependabot[bot]
5e888ab48e chore(deps): bump wildmatch from 2.5.0 to 2.6.1 in /codex-rs (#7716)
Bumps [wildmatch](https://github.com/becheran/wildmatch) from 2.5.0 to
2.6.1.
<details>
<summary>Commits</summary>
<ul>
<li><a
href="ca6568be7e"><code>ca6568b</code></a>
chore: Release wildmatch version 2.6.1</li>
<li><a
href="513c5ab967"><code>513c5ab</code></a>
docs: fix broken links</li>
<li><a
href="fe47b5f750"><code>fe47b5f</code></a>
chore: use latest mlc version</li>
<li><a
href="4d05f9f3d1"><code>4d05f9f</code></a>
Merge pull request <a
href="https://redirect.github.com/becheran/wildmatch/issues/30">#30</a>
from arifd/patch-1</li>
<li><a
href="26114f73de"><code>26114f7</code></a>
unify example pattern used in WildMatchPattern examples</li>
<li><a
href="32c36f5113"><code>32c36f5</code></a>
chore: Release wildmatch version 2.6.0</li>
<li><a
href="4777964a65"><code>4777964</code></a>
Merge pull request <a
href="https://redirect.github.com/becheran/wildmatch/issues/29">#29</a>
from arifd/prevent-ambiguous-same-single-multi-wildcard</li>
<li><a
href="3a5bf1b4f6"><code>3a5bf1b</code></a>
prevent ambiguous same single multi wildcard</li>
<li>See full diff in <a
href="https://github.com/becheran/wildmatch/compare/v2.5.0...v2.6.1">compare
view</a></li>
</ul>
</details>
<br />


[![Dependabot compatibility
score](https://dependabot-badges.githubapp.com/badges/compatibility_score?dependency-name=wildmatch&package-manager=cargo&previous-version=2.5.0&new-version=2.6.1)](https://docs.github.com/en/github/managing-security-vulnerabilities/about-dependabot-security-updates#about-compatibility-scores)

Dependabot will resolve any conflicts with this PR as long as you don't
alter it yourself. You can also trigger a rebase manually by commenting
`@dependabot rebase`.

[//]: # (dependabot-automerge-start)
[//]: # (dependabot-automerge-end)

---

<details>
<summary>Dependabot commands and options</summary>
<br />

You can trigger Dependabot actions by commenting on this PR:
- `@dependabot rebase` will rebase this PR
- `@dependabot recreate` will recreate this PR, overwriting any edits
that have been made to it
- `@dependabot merge` will merge this PR after your CI passes on it
- `@dependabot squash and merge` will squash and merge this PR after
your CI passes on it
- `@dependabot cancel merge` will cancel a previously requested merge
and block automerging
- `@dependabot reopen` will reopen this PR if it is closed
- `@dependabot close` will close this PR and stop Dependabot recreating
it. You can achieve the same result by closing it manually
- `@dependabot show <dependency name> ignore conditions` will show all
of the ignore conditions of the specified dependency
- `@dependabot ignore this major version` will close this PR and stop
Dependabot creating any more for this major version (unless you reopen
the PR or upgrade to it yourself)
- `@dependabot ignore this minor version` will close this PR and stop
Dependabot creating any more for this minor version (unless you reopen
the PR or upgrade to it yourself)
- `@dependabot ignore this dependency` will close this PR and stop
Dependabot creating any more for this dependency (unless you reopen the
PR or upgrade to it yourself)


</details>

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-12-08 17:13:51 +00:00
dependabot[bot]
9fa9e3e7bb chore(deps): bump insta from 1.43.2 to 1.44.3 in /codex-rs (#7715)
Bumps [insta](https://github.com/mitsuhiko/insta) from 1.43.2 to 1.44.3.
<details>
<summary>Release notes</summary>
<p><em>Sourced from <a
href="https://github.com/mitsuhiko/insta/releases">insta's
releases</a>.</em></p>
<blockquote>
<h2>1.44.3</h2>
<h2>Release Notes</h2>
<ul>
<li>Fix a regression in 1.44.2 where merge conflict detection was too
aggressive, incorrectly flagging snapshot content containing
<code>======</code> or similar patterns as conflicts. <a
href="https://redirect.github.com/mitsuhiko/insta/issues/832">#832</a></li>
<li>Fix a regression in 1.42.2 where inline snapshot updates would
corrupt the file when code preceded the macro (e.g., <code>let output =
assert_snapshot!(...)</code>). <a
href="https://redirect.github.com/mitsuhiko/insta/issues/833">#833</a></li>
</ul>
<h2>Install cargo-insta 1.44.3</h2>
<h3>Install prebuilt binaries via shell script</h3>
<pre lang="sh"><code>curl --proto '=https' --tlsv1.2 -LsSf
https://github.com/mitsuhiko/insta/releases/download/1.44.3/cargo-insta-installer.sh
| sh
</code></pre>
<h3>Install prebuilt binaries via powershell script</h3>
<pre lang="sh"><code>powershell -ExecutionPolicy Bypass -c &quot;irm
https://github.com/mitsuhiko/insta/releases/download/1.44.3/cargo-insta-installer.ps1
| iex&quot;
</code></pre>
<h2>Download cargo-insta 1.44.3</h2>
<table>
<thead>
<tr>
<th>File</th>
<th>Platform</th>
<th>Checksum</th>
</tr>
</thead>
<tbody>
<tr>
<td><a
href="https://github.com/mitsuhiko/insta/releases/download/1.44.3/cargo-insta-aarch64-apple-darwin.tar.xz">cargo-insta-aarch64-apple-darwin.tar.xz</a></td>
<td>Apple Silicon macOS</td>
<td><a
href="https://github.com/mitsuhiko/insta/releases/download/1.44.3/cargo-insta-aarch64-apple-darwin.tar.xz.sha256">checksum</a></td>
</tr>
<tr>
<td><a
href="https://github.com/mitsuhiko/insta/releases/download/1.44.3/cargo-insta-x86_64-apple-darwin.tar.xz">cargo-insta-x86_64-apple-darwin.tar.xz</a></td>
<td>Intel macOS</td>
<td><a
href="https://github.com/mitsuhiko/insta/releases/download/1.44.3/cargo-insta-x86_64-apple-darwin.tar.xz.sha256">checksum</a></td>
</tr>
<tr>
<td><a
href="https://github.com/mitsuhiko/insta/releases/download/1.44.3/cargo-insta-x86_64-pc-windows-msvc.zip">cargo-insta-x86_64-pc-windows-msvc.zip</a></td>
<td>x64 Windows</td>
<td><a
href="https://github.com/mitsuhiko/insta/releases/download/1.44.3/cargo-insta-x86_64-pc-windows-msvc.zip.sha256">checksum</a></td>
</tr>
<tr>
<td><a
href="https://github.com/mitsuhiko/insta/releases/download/1.44.3/cargo-insta-x86_64-unknown-linux-gnu.tar.xz">cargo-insta-x86_64-unknown-linux-gnu.tar.xz</a></td>
<td>x64 Linux</td>
<td><a
href="https://github.com/mitsuhiko/insta/releases/download/1.44.3/cargo-insta-x86_64-unknown-linux-gnu.tar.xz.sha256">checksum</a></td>
</tr>
<tr>
<td><a
href="https://github.com/mitsuhiko/insta/releases/download/1.44.3/cargo-insta-x86_64-unknown-linux-musl.tar.xz">cargo-insta-x86_64-unknown-linux-musl.tar.xz</a></td>
<td>x64 MUSL Linux</td>
<td><a
href="https://github.com/mitsuhiko/insta/releases/download/1.44.3/cargo-insta-x86_64-unknown-linux-musl.tar.xz.sha256">checksum</a></td>
</tr>
</tbody>
</table>
<h2>1.44.2</h2>
<h2>Release Notes</h2>
<ul>
<li>Fix a rare backward compatibility issue where inline snapshots using
an uncommon legacy format (single-line content stored in multiline raw
strings) could fail to match after 1.44.0. <a
href="https://redirect.github.com/mitsuhiko/insta/issues/830">#830</a></li>
<li>Handle merge conflicts in snapshot files gracefully. When a snapshot
file contains git merge conflict markers, insta now detects them and
treats the snapshot as missing, allowing tests to continue and create a
new pending snapshot for review. <a
href="https://redirect.github.com/mitsuhiko/insta/issues/829">#829</a></li>
<li>Skip nextest_doctest tests when cargo-nextest is not installed. <a
href="https://redirect.github.com/mitsuhiko/insta/issues/826">#826</a></li>
<li>Fix functional tests failing under nextest due to inherited
<code>NEXTEST_RUN_ID</code> environment variable. <a
href="https://redirect.github.com/mitsuhiko/insta/issues/824">#824</a></li>
</ul>
<h2>Install cargo-insta 1.44.2</h2>
<h3>Install prebuilt binaries via shell script</h3>
<pre lang="sh"><code>curl --proto '=https' --tlsv1.2 -LsSf
https://github.com/mitsuhiko/insta/releases/download/1.44.2/cargo-insta-installer.sh
| sh
</code></pre>
<h3>Install prebuilt binaries via powershell script</h3>
<pre lang="sh"><code>powershell -ExecutionPolicy Bypass -c &quot;irm
https://github.com/mitsuhiko/insta/releases/download/1.44.2/cargo-insta-installer.ps1
| iex&quot;
&lt;/tr&gt;&lt;/table&gt; 
</code></pre>
</blockquote>
<p>... (truncated)</p>
</details>
<details>
<summary>Changelog</summary>
<p><em>Sourced from <a
href="https://github.com/mitsuhiko/insta/blob/master/CHANGELOG.md">insta's
changelog</a>.</em></p>
<blockquote>
<h2>1.44.3</h2>
<ul>
<li>Fix a regression in 1.44.2 where merge conflict detection was too
aggressive, incorrectly flagging snapshot content containing
<code>======</code> or similar patterns as conflicts. <a
href="https://redirect.github.com/mitsuhiko/insta/issues/832">#832</a></li>
<li>Fix a regression in 1.42.2 where inline snapshot updates would
corrupt the file when code preceded the macro (e.g., <code>let output =
assert_snapshot!(...)</code>). <a
href="https://redirect.github.com/mitsuhiko/insta/issues/833">#833</a></li>
</ul>
<h2>1.44.2</h2>
<ul>
<li>Fix a rare backward compatibility issue where inline snapshots using
an uncommon legacy format (single-line content stored in multiline raw
strings) could fail to match after 1.44.0. <a
href="https://redirect.github.com/mitsuhiko/insta/issues/830">#830</a></li>
<li>Handle merge conflicts in snapshot files gracefully. When a snapshot
file contains git merge conflict markers, insta now detects them and
treats the snapshot as missing, allowing tests to continue and create a
new pending snapshot for review. <a
href="https://redirect.github.com/mitsuhiko/insta/issues/829">#829</a></li>
<li>Skip nextest_doctest tests when cargo-nextest is not installed. <a
href="https://redirect.github.com/mitsuhiko/insta/issues/826">#826</a></li>
<li>Fix functional tests failing under nextest due to inherited
<code>NEXTEST_RUN_ID</code> environment variable. <a
href="https://redirect.github.com/mitsuhiko/insta/issues/824">#824</a></li>
</ul>
<h2>1.44.1</h2>
<ul>
<li>Add <code>--dnd</code> alias for
<code>--disable-nextest-doctest</code> flag to make it easier to silence
the deprecation warning. <a
href="https://redirect.github.com/mitsuhiko/insta/issues/822">#822</a></li>
<li>Update cargo-dist to 0.30.2 and fix Windows runner to use
windows-2022. <a
href="https://redirect.github.com/mitsuhiko/insta/issues/821">#821</a></li>
</ul>
<h2>1.44.0</h2>
<ul>
<li>Added non-interactive snapshot review and reject modes for use in
non-TTY environments
(LLMs, CI pipelines, scripts). <code>cargo insta review --snapshot
&lt;path&gt;</code> and
<code>cargo insta reject --snapshot &lt;path&gt;</code> now work without
a terminal. Enhanced
<code>pending-snapshots</code> output with usage instructions and
workspace-relative paths. <a
href="https://redirect.github.com/mitsuhiko/insta/issues/815">#815</a></li>
<li>Add <code>--disable-nextest-doctest</code> flag to <code>cargo insta
test</code> to disable running doctests with
nextest. Shows a deprecation warning when nextest is used with doctests
without this flag, to prepare <code>cargo insta</code> to no longer run
a separate doctest process when using nextest in the future. <a
href="https://redirect.github.com/mitsuhiko/insta/issues/803">#803</a></li>
<li>Add ergonomic <code>--test-runner-fallback</code> /
<code>--no-test-runner-fallback</code> flags to <code>cargo insta
test</code>. <a
href="https://redirect.github.com/mitsuhiko/insta/issues/811">#811</a></li>
<li>Apply redactions to snapshot metadata. <a
href="https://redirect.github.com/mitsuhiko/insta/issues/813">#813</a></li>
<li>Remove confusing 'previously unseen snapshot' message. <a
href="https://redirect.github.com/mitsuhiko/insta/issues/812">#812</a></li>
<li>Speed up JSON float rendering. <a
href="https://redirect.github.com/mitsuhiko/insta/issues/806">#806</a>
(<a href="https://github.com/nyurik"><code>@​nyurik</code></a>)</li>
<li>Allow globset version up to 0.4.16. <a
href="https://redirect.github.com/mitsuhiko/insta/issues/810">#810</a>
(<a href="https://github.com/g0hl1n"><code>@​g0hl1n</code></a>)</li>
<li>Improve documentation. <a
href="https://redirect.github.com/mitsuhiko/insta/issues/814">#814</a>
(<a href="https://github.com/tshepang"><code>@​tshepang</code></a>)</li>
<li>We no longer trim starting newlines during assertions, which allows
asserting
the number of leading newlines match. Existing assertions with different
leading newlines will pass and print a warning suggesting running with
<code>--force-update-snapshots</code>. They may fail in the future.
(Note that we still
currently allow differing <em>trailing</em> newlines, though may adjust
this in the
future). <a
href="https://redirect.github.com/mitsuhiko/insta/issues/563">#563</a></li>
</ul>
</blockquote>
</details>
<details>
<summary>Commits</summary>
<ul>
<li><a
href="dcbb11fdc1"><code>dcbb11f</code></a>
Prepare release 1.44.3 (<a
href="https://redirect.github.com/mitsuhiko/insta/issues/838">#838</a>)</li>
<li><a
href="3b9ec12268"><code>3b9ec12</code></a>
Refine test name &amp; description (<a
href="https://redirect.github.com/mitsuhiko/insta/issues/837">#837</a>)</li>
<li><a
href="ee4e1ea39c"><code>ee4e1ea</code></a>
Handle unparsable snapshot files gracefully (<a
href="https://redirect.github.com/mitsuhiko/insta/issues/836">#836</a>)</li>
<li><a
href="778f7336b4"><code>778f733</code></a>
Fix for code before macros, such as <code>let foo =
assert_snapshot!</code> (<a
href="https://redirect.github.com/mitsuhiko/insta/issues/835">#835</a>)</li>
<li><a
href="6cb41aff12"><code>6cb41af</code></a>
Prepare release 1.44.2 (<a
href="https://redirect.github.com/mitsuhiko/insta/issues/831">#831</a>)</li>
<li><a
href="8838b2fe44"><code>8838b2f</code></a>
Handle merge conflicts in snapshot files gracefully (<a
href="https://redirect.github.com/mitsuhiko/insta/issues/829">#829</a>)</li>
<li><a
href="e55ce9946d"><code>e55ce99</code></a>
Fix backward compatibility for legacy inline snapshot format (<a
href="https://redirect.github.com/mitsuhiko/insta/issues/830">#830</a>)</li>
<li><a
href="d44dd422b1"><code>d44dd42</code></a>
Skip nextest_doctest tests when cargo-nextest is not installed (<a
href="https://redirect.github.com/mitsuhiko/insta/issues/826">#826</a>)</li>
<li><a
href="a711bafd63"><code>a711baf</code></a>
Fix functional tests failing under nextest (<a
href="https://redirect.github.com/mitsuhiko/insta/issues/824">#824</a>)</li>
<li><a
href="ba9ea5148c"><code>ba9ea51</code></a>
Prepare release 1.44.1 (<a
href="https://redirect.github.com/mitsuhiko/insta/issues/823">#823</a>)</li>
<li>Additional commits viewable in <a
href="https://github.com/mitsuhiko/insta/compare/1.43.2...1.44.3">compare
view</a></li>
</ul>
</details>
<br />


[![Dependabot compatibility
score](https://dependabot-badges.githubapp.com/badges/compatibility_score?dependency-name=insta&package-manager=cargo&previous-version=1.43.2&new-version=1.44.3)](https://docs.github.com/en/github/managing-security-vulnerabilities/about-dependabot-security-updates#about-compatibility-scores)

Dependabot will resolve any conflicts with this PR as long as you don't
alter it yourself. You can also trigger a rebase manually by commenting
`@dependabot rebase`.

[//]: # (dependabot-automerge-start)
[//]: # (dependabot-automerge-end)

---

<details>
<summary>Dependabot commands and options</summary>
<br />

You can trigger Dependabot actions by commenting on this PR:
- `@dependabot rebase` will rebase this PR
- `@dependabot recreate` will recreate this PR, overwriting any edits
that have been made to it
- `@dependabot merge` will merge this PR after your CI passes on it
- `@dependabot squash and merge` will squash and merge this PR after
your CI passes on it
- `@dependabot cancel merge` will cancel a previously requested merge
and block automerging
- `@dependabot reopen` will reopen this PR if it is closed
- `@dependabot close` will close this PR and stop Dependabot recreating
it. You can achieve the same result by closing it manually
- `@dependabot show <dependency name> ignore conditions` will show all
of the ignore conditions of the specified dependency
- `@dependabot ignore this major version` will close this PR and stop
Dependabot creating any more for this major version (unless you reopen
the PR or upgrade to it yourself)
- `@dependabot ignore this minor version` will close this PR and stop
Dependabot creating any more for this minor version (unless you reopen
the PR or upgrade to it yourself)
- `@dependabot ignore this dependency` will close this PR and stop
Dependabot creating any more for this dependency (unless you reopen the
PR or upgrade to it yourself)


</details>

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-12-08 08:59:19 -08:00
dependabot[bot]
7a6d6090d7 chore(deps): bump derive_more from 2.0.1 to 2.1.0 in /codex-rs (#7714)
Bumps [derive_more](https://github.com/JelteF/derive_more) from 2.0.1 to
2.1.0.
<details>
<summary>Release notes</summary>
<p><em>Sourced from <a
href="https://github.com/JelteF/derive_more/releases">derive_more's
releases</a>.</em></p>
<blockquote>
<h2>2.1.0</h2>
<h3>Added</h3>
<ul>
<li>Support <code>#[display(rename_all =
&quot;&lt;casing&gt;&quot;)]</code> attribute to change output for
implicit naming of unit enum variants or unit structs when deriving
<code>Display</code>. (<a
href="https://redirect.github.com/JelteF/derive_more/pull/443">#443</a>)</li>
<li>Support <code>#[from_str(rename_all =
&quot;&lt;casing&gt;&quot;)]</code> attribute for unit enum variants and
unit structs when deriving <code>FromStr</code>. (<a
href="https://redirect.github.com/JelteF/derive_more/pull/467">#467</a>)</li>
<li>Support <code>Option</code> fields for <code>Error::source()</code>
in <code>Error</code> derive. (<a
href="https://redirect.github.com/JelteF/derive_more/pull/459">#459</a>)</li>
<li>Support structs with no fields in <code>FromStr</code> derive. (<a
href="https://redirect.github.com/JelteF/derive_more/pull/469">#469</a>)</li>
<li>Add <code>PartialEq</code> derive similar to <code>std</code>'s one,
but considering generics correctly, and implementing <code>ne()</code>
method as well. (<a
href="https://redirect.github.com/JelteF/derive_more/pull/473">#473</a>,
<a
href="https://redirect.github.com/JelteF/derive_more/pull/475">#475</a>)</li>
<li>Add <code>Eq</code> derive similar to <code>std</code>'s one, but
considering generics correctly. (<a
href="https://redirect.github.com/JelteF/derive_more/pull/479">#479</a>)</li>
<li>Proxy-pass <code>#[allow]</code>/<code>#[expect]</code> attributes
of the type in <code>Constructor</code> derive. (<a
href="https://redirect.github.com/JelteF/derive_more/pull/477">#477</a>)</li>
<li>Support <code>Deref</code> and <code>DerefMut</code> derives for
enums. (<a
href="https://redirect.github.com/JelteF/derive_more/pull/485">#485</a>)</li>
<li>Support custom error in <code>FromStr</code> derive. (<a
href="https://redirect.github.com/JelteF/derive_more/pull/494">#494</a>)</li>
<li>Support custom error in <code>TryInto</code> derive. (<a
href="https://redirect.github.com/JelteF/derive_more/pull/503">#503</a>)</li>
<li>Support skipping fields in <code>Add</code>-like,
<code>AddAssign</code>-like, <code>Mul</code>-like and
<code>MulAssign</code>-like derives. (<a
href="https://redirect.github.com/JelteF/derive_more/pull/472">#472</a>)</li>
</ul>
<h3>Changed</h3>
<ul>
<li>The minimum supported Rust version (MSRV) is now Rust 1.81. (<a
href="https://redirect.github.com/JelteF/derive_more/pull/466">#466</a>)</li>
<li><code>Add</code>-like, <code>AddAssign</code>-like,
<code>Mul</code>-like and <code>MulAssign</code>-like derives now infer
trait bounds for generics structurally (bound field types instead of
type parameters directly). (<a
href="https://redirect.github.com/JelteF/derive_more/pull/472">#472</a>)</li>
</ul>
<h3>Fixed</h3>
<ul>
<li>Suppress deprecation warnings in generated code. (<a
href="https://redirect.github.com/JelteF/derive_more/pull/454">#454</a>)</li>
<li>Silent no-op when <code>#[try_from(repr)]</code> attribute is not
specified for <code>TryFrom</code> derive. (<a
href="https://redirect.github.com/JelteF/derive_more/pull/458">#458</a>)</li>
<li>Missing trait bounds in <code>AsRef</code>/<code>AsMut</code>
derives when associative types are involved. (<a
href="https://redirect.github.com/JelteF/derive_more/pull/474">#474</a>)</li>
<li>Erroneous code generated in <code>Try</code>/<code>TryInto</code>
derives when <code>Self</code> type is present in the struct or enum
definition. (<a
href="https://redirect.github.com/JelteF/derive_more/pull/489">#489</a>)</li>
<li>Dependency on unstable
<code>feature(error_generic_member_access)</code> in <code>Error</code>
derive when using <code>Backtrace</code> on a non-nightly toolchain. (<a
href="https://redirect.github.com/JelteF/derive_more/pull/513">#513</a>)</li>
<li>Broken support for <code>#[&lt;display-trait&gt;(&quot;default
formatting&quot;)]</code> attribute without <code>{_variant}</code>
being used as default for enum variants without explicit formatting. (<a
href="https://redirect.github.com/JelteF/derive_more/pull/495">#495</a>)</li>
</ul>
<h2>New Contributors</h2>
<ul>
<li><a href="https://github.com/jasl"><code>@​jasl</code></a> made their
first contribution in <a
href="https://redirect.github.com/JelteF/derive_more/pull/453">JelteF/derive_more#453</a></li>
<li><a href="https://github.com/aborgna-q"><code>@​aborgna-q</code></a>
made their first contribution in <a
href="https://redirect.github.com/JelteF/derive_more/pull/454">JelteF/derive_more#454</a></li>
<li><a
href="https://github.com/maxime-bruno"><code>@​maxime-bruno</code></a>
made their first contribution in <a
href="https://redirect.github.com/JelteF/derive_more/pull/461">JelteF/derive_more#461</a></li>
<li><a
href="https://github.com/Cheban1996"><code>@​Cheban1996</code></a> made
their first contribution in <a
href="https://redirect.github.com/JelteF/derive_more/pull/477">JelteF/derive_more#477</a></li>
<li><a href="https://github.com/kiendang"><code>@​kiendang</code></a>
made their first contribution in <a
href="https://redirect.github.com/JelteF/derive_more/pull/486">JelteF/derive_more#486</a></li>
<li><a
href="https://github.com/goldlinker"><code>@​goldlinker</code></a> made
their first contribution in <a
href="https://redirect.github.com/JelteF/derive_more/pull/509">JelteF/derive_more#509</a></li>
<li><a href="https://github.com/CJKay"><code>@​CJKay</code></a> made
their first contribution in <a
href="https://redirect.github.com/JelteF/derive_more/pull/513">JelteF/derive_more#513</a></li>
<li><a
href="https://github.com/ErmitaVulpe"><code>@​ErmitaVulpe</code></a>
made their first contribution in <a
href="https://redirect.github.com/JelteF/derive_more/pull/472">JelteF/derive_more#472</a></li>
</ul>
<p><strong>Full Changelog</strong>: <a
href="https://github.com/JelteF/derive_more/compare/v2.0.1...v2.1.0">https://github.com/JelteF/derive_more/compare/v2.0.1...v2.1.0</a></p>
</blockquote>
</details>
<details>
<summary>Changelog</summary>
<p><em>Sourced from <a
href="https://github.com/JelteF/derive_more/blob/master/CHANGELOG.md">derive_more's
changelog</a>.</em></p>
<blockquote>
<h2>2.1.0 - 2025-12-02</h2>
<h3>Added</h3>
<ul>
<li>Support <code>#[display(rename_all =
&quot;&lt;casing&gt;&quot;)]</code> attribute to change output for
implicit naming of unit enum variants or unit structs when deriving
<code>Display</code>.
(<a
href="https://redirect.github.com/JelteF/derive_more/pull/443">#443</a>)</li>
<li>Support <code>#[from_str(rename_all =
&quot;&lt;casing&gt;&quot;)]</code> attribute for unit enum variants
and unit structs when deriving <code>FromStr</code>.
(<a
href="https://redirect.github.com/JelteF/derive_more/pull/467">#467</a>)</li>
<li>Support <code>Option</code> fields for <code>Error::source()</code>
in <code>Error</code> derive.
(<a
href="https://redirect.github.com/JelteF/derive_more/pull/459">#459</a>)</li>
<li>Support structs with no fields in <code>FromStr</code> derive.
(<a
href="https://redirect.github.com/JelteF/derive_more/pull/469">#469</a>)</li>
<li>Add <code>PartialEq</code> derive similar to <code>std</code>'s one,
but considering generics correctly,
and implementing <code>ne()</code> method as well.
(<a
href="https://redirect.github.com/JelteF/derive_more/pull/473">#473</a>,
<a
href="https://redirect.github.com/JelteF/derive_more/pull/475">#475</a>)</li>
<li>Add <code>Eq</code> derive similar to <code>std</code>'s one, but
considering generics correctly.
(<a
href="https://redirect.github.com/JelteF/derive_more/pull/479">#479</a>)</li>
<li>Proxy-pass <code>#[allow]</code>/<code>#[expect]</code> attributes
of the type in <code>Constructor</code> derive.
(<a
href="https://redirect.github.com/JelteF/derive_more/pull/477">#477</a>)</li>
<li>Support <code>Deref</code> and <code>DerefMut</code> derives for
enums.
(<a
href="https://redirect.github.com/JelteF/derive_more/pull/485">#485</a>)</li>
<li>Support custom error in <code>FromStr</code> derive.
(<a
href="https://redirect.github.com/JelteF/derive_more/pull/494">#494</a>)</li>
<li>Support custom error in <code>TryInto</code> derive.
(<a
href="https://redirect.github.com/JelteF/derive_more/pull/503">#503</a>)</li>
<li>Support skipping fields in <code>Add</code>-like,
<code>AddAssign</code>-like, <code>Mul</code>-like and
<code>MulAssign</code>-like derives.
(<a
href="https://redirect.github.com/JelteF/derive_more/pull/472">#472</a>)</li>
</ul>
<h3>Changed</h3>
<ul>
<li>The minimum supported Rust version (MSRV) is now Rust 1.81.
(<a
href="https://redirect.github.com/JelteF/derive_more/pull/466">#466</a>)</li>
<li><code>Add</code>-like, <code>AddAssign</code>-like,
<code>Mul</code>-like and <code>MulAssign</code>-like derives now
infer trait bounds for generics structurally (bound field types instead
of
type parameters directly).
(<a
href="https://redirect.github.com/JelteF/derive_more/pull/472">#472</a>)</li>
</ul>
<h3>Fixed</h3>
<ul>
<li>Suppress deprecation warnings in generated code.
(<a
href="https://redirect.github.com/JelteF/derive_more/pull/454">#454</a>)</li>
<li>Silent no-op when <code>#[try_from(repr)]</code> attribute is not
specified for <code>TryFrom</code> derive.
(<a
href="https://redirect.github.com/JelteF/derive_more/pull/458">#458</a>)</li>
<li>Missing trait bounds in <code>AsRef</code>/<code>AsMut</code>
derives when associative types are involved.
(<a
href="https://redirect.github.com/JelteF/derive_more/pull/474">#474</a>)</li>
<li>Erroneous code generated in <code>Try</code>/<code>TryInto</code>
derives when <code>Self</code> type is present in</li>
</ul>
<!-- raw HTML omitted -->
</blockquote>
<p>... (truncated)</p>
</details>
<details>
<summary>Commits</summary>
<ul>
<li><a
href="c354bad7c9"><code>c354bad</code></a>
Prepare 2.1.0 release (<a
href="https://redirect.github.com/JelteF/derive_more/issues/521">#521</a>)</li>
<li><a
href="983875f8ab"><code>983875f</code></a>
Allow using enum-level attributes for non-<code>Display</code>
formatting traits as defa...</li>
<li><a
href="2d3805b66f"><code>2d3805b</code></a>
Allow skipping fields for
<code>Add</code>/<code>AddAssign</code>/<code>Mul</code>/<code>MulAssign</code>-like
derives (#...</li>
<li><a
href="1b5d314b8c"><code>1b5d314</code></a>
Upgrade <code>convert_case</code> requirement from 0.9 to 0.10 version
(<a
href="https://redirect.github.com/JelteF/derive_more/issues/520">#520</a>)</li>
<li><a
href="c32d0a0a05"><code>c32d0a0</code></a>
Upgrade <code>actions/checkout</code> from 5 to 6 version (<a
href="https://redirect.github.com/JelteF/derive_more/issues/519">#519</a>)</li>
<li><a
href="905f5a3020"><code>905f5a3</code></a>
Upgrade <code>convert_case</code> crate from 0.8 to 0.9 version (<a
href="https://redirect.github.com/JelteF/derive_more/issues/517">#517</a>)</li>
<li><a
href="8e9104d538"><code>8e9104d</code></a>
Support <code>syn::ExprCall</code> and <code>syn::ExprClosure</code> for
custom errors (<a
href="https://redirect.github.com/JelteF/derive_more/issues/516">#516</a>,
<a
href="https://redirect.github.com/JelteF/derive_more/issues/112">#112</a>)</li>
<li><a
href="be3edc4c9f"><code>be3edc4</code></a>
Update <code>compile_fail</code> tests for 1.91 Rust (<a
href="https://redirect.github.com/JelteF/derive_more/issues/515">#515</a>)</li>
<li><a
href="929dd417ce"><code>929dd41</code></a>
Support custom error type in <code>TryInto</code> derive (<a
href="https://redirect.github.com/JelteF/derive_more/issues/503">#503</a>,
<a
href="https://redirect.github.com/JelteF/derive_more/issues/396">#396</a>)</li>
<li><a
href="4fc682791a"><code>4fc6827</code></a>
Remove unstable feature requirement when deriving
<code>Backtrace</code>d <code>Error</code> (<a
href="https://redirect.github.com/JelteF/derive_more/issues/513">#513</a>,...</li>
<li>Additional commits viewable in <a
href="https://github.com/JelteF/derive_more/compare/v2.0.1...v2.1.0">compare
view</a></li>
</ul>
</details>
<br />


[![Dependabot compatibility
score](https://dependabot-badges.githubapp.com/badges/compatibility_score?dependency-name=derive_more&package-manager=cargo&previous-version=2.0.1&new-version=2.1.0)](https://docs.github.com/en/github/managing-security-vulnerabilities/about-dependabot-security-updates#about-compatibility-scores)

Dependabot will resolve any conflicts with this PR as long as you don't
alter it yourself. You can also trigger a rebase manually by commenting
`@dependabot rebase`.

[//]: # (dependabot-automerge-start)
[//]: # (dependabot-automerge-end)

---

<details>
<summary>Dependabot commands and options</summary>
<br />

You can trigger Dependabot actions by commenting on this PR:
- `@dependabot rebase` will rebase this PR
- `@dependabot recreate` will recreate this PR, overwriting any edits
that have been made to it
- `@dependabot merge` will merge this PR after your CI passes on it
- `@dependabot squash and merge` will squash and merge this PR after
your CI passes on it
- `@dependabot cancel merge` will cancel a previously requested merge
and block automerging
- `@dependabot reopen` will reopen this PR if it is closed
- `@dependabot close` will close this PR and stop Dependabot recreating
it. You can achieve the same result by closing it manually
- `@dependabot show <dependency name> ignore conditions` will show all
of the ignore conditions of the specified dependency
- `@dependabot ignore this major version` will close this PR and stop
Dependabot creating any more for this major version (unless you reopen
the PR or upgrade to it yourself)
- `@dependabot ignore this minor version` will close this PR and stop
Dependabot creating any more for this minor version (unless you reopen
the PR or upgrade to it yourself)
- `@dependabot ignore this dependency` will close this PR and stop
Dependabot creating any more for this dependency (unless you reopen the
PR or upgrade to it yourself)


</details>

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-12-08 08:58:50 -08:00
dependabot[bot]
701f42b74b chore(deps): bump ts-rs from 11.0.1 to 11.1.0 in /codex-rs (#7713)
Bumps [ts-rs](https://github.com/Aleph-Alpha/ts-rs) from 11.0.1 to
11.1.0.
<details>
<summary>Release notes</summary>
<p><em>Sourced from <a
href="https://github.com/Aleph-Alpha/ts-rs/releases">ts-rs's
releases</a>.</em></p>
<blockquote>
<h2>v11.1.0</h2>
<p>Today, we're happy to publish a small follow-up to v11.0.1!</p>
<p>This release fixes a nasty build failure when using the
<code>format</code> feature.
<strong>Note:</strong> For those that use the <code>format</code>
feature, this release bumps the MSRV to 1.88. We'd have preferred to do
this in a major release, but felt this was acceptable since the build
was broken by one of the dependencies anyway.</p>
<h1>New features</h1>
<h2>TypeScript enums with <code>#[ts(repr(enum))</code></h2>
<p><code>#[ts(repr(enum))</code> instructs ts-rs to generate an
<code>enum</code>, instead of a <code>type</code> for your rust
enum.</p>
<pre lang="rust"><code>#[derive(TS)]
#[ts(repr(enum))]
enum Role {
    User,
    Admin,
}
// will generate `export enum Role { &quot;User&quot;, &quot;Admin&quot;
}`
</code></pre>
<p>Discriminants are preserved, and you can use the variant's name as
discriminant instead using <code>#[ts(repr(enum = name))]</code></p>
<h2><code>#[ts(optional_fields)]</code> in enums</h2>
<p>The <code>#[ts(optional_fields)]</code> attribute can now be applied
directly to enums, or even to individual enum variants.</p>
<h2>Control over file extensions in imports</h2>
<p>Normally, we generate <code>import { Type } from
&quot;file&quot;</code> statements. In some scenarios though, it might
be necessary to use a <code>.ts</code> or even <code>.js</code>
extension instead.<br />
This is now possible by setting the <code>TS_RS_IMPORT_EXTENSION</code>
environment variable.</p>
<blockquote>
<p>Note: With the introduction of this feature, we deprecate the
<code>import-esm</code> cargo feature. It will be removed in a future
major release.</p>
</blockquote>
<h2>Full changelog</h2>
<ul>
<li>Regression: <code>#[ts(optional)]</code> with
<code>#[ts(type)]</code> by <a
href="https://github.com/NyxCode"><code>@​NyxCode</code></a> in <a
href="https://redirect.github.com/Aleph-Alpha/ts-rs/pull/416">Aleph-Alpha/ts-rs#416</a></li>
<li>release v11.0.1 by <a
href="https://github.com/NyxCode"><code>@​NyxCode</code></a> in <a
href="https://redirect.github.com/Aleph-Alpha/ts-rs/pull/417">Aleph-Alpha/ts-rs#417</a></li>
<li>Make <code>rename_all</code> compatible with tuple and unit structs
as a no-op attribute by <a
href="https://github.com/gustavo-shigueo"><code>@​gustavo-shigueo</code></a>
in <a
href="https://redirect.github.com/Aleph-Alpha/ts-rs/pull/422">Aleph-Alpha/ts-rs#422</a></li>
<li>Replace <code>import-esm</code> with
<code>TS_RS_IMPORT_EXTENSION</code> by <a
href="https://github.com/gustavo-shigueo"><code>@​gustavo-shigueo</code></a>
in <a
href="https://redirect.github.com/Aleph-Alpha/ts-rs/pull/423">Aleph-Alpha/ts-rs#423</a></li>
<li>Updated chrono Duration emitted type by <a
href="https://github.com/fxf8"><code>@​fxf8</code></a> in <a
href="https://redirect.github.com/Aleph-Alpha/ts-rs/pull/434">Aleph-Alpha/ts-rs#434</a></li>
<li>Add optional_fields to enum by <a
href="https://github.com/gustavo-shigueo"><code>@​gustavo-shigueo</code></a>
in <a
href="https://redirect.github.com/Aleph-Alpha/ts-rs/pull/432">Aleph-Alpha/ts-rs#432</a></li>
<li>Add <code>#[ts(repr(enum)]</code> attribute by <a
href="https://github.com/gustavo-shigueo"><code>@​gustavo-shigueo</code></a>
in <a
href="https://redirect.github.com/Aleph-Alpha/ts-rs/pull/425">Aleph-Alpha/ts-rs#425</a></li>
<li>Fix build with <code>format</code> feature by <a
href="https://github.com/gustavo-shigueo"><code>@​gustavo-shigueo</code></a>
in <a
href="https://redirect.github.com/Aleph-Alpha/ts-rs/pull/438">Aleph-Alpha/ts-rs#438</a></li>
</ul>
<h2>New Contributors</h2>
<ul>
<li><a href="https://github.com/fxf8"><code>@​fxf8</code></a> made their
first contribution in <a
href="https://redirect.github.com/Aleph-Alpha/ts-rs/pull/434">Aleph-Alpha/ts-rs#434</a></li>
</ul>
</blockquote>
</details>
<details>
<summary>Changelog</summary>
<p><em>Sourced from <a
href="https://github.com/Aleph-Alpha/ts-rs/blob/main/CHANGELOG.md">ts-rs's
changelog</a>.</em></p>
<blockquote>
<h1>11.1.0</h1>
<h3>Features</h3>
<ul>
<li>Add <code>#[ts(repr(enum))]</code> attribute (<a
href="https://redirect.github.com/Aleph-Alpha/ts-rs/pull/425">#425</a>)</li>
<li>Add support for <code>#[ts(optional_fields)]</code> in enums and
enum variants (<a
href="https://redirect.github.com/Aleph-Alpha/ts-rs/pull/432">#432</a>)</li>
<li>Deprecate <code>import-esm</code> cargo feature in favour of
<code>RS_RS_IMPORT_EXTENSION</code> (<a
href="https://redirect.github.com/Aleph-Alpha/ts-rs/pull/423">#423</a>)</li>
</ul>
<h3>Fixes</h3>
<ul>
<li>Fix bindings for <code>chrono::Duration</code> (<a
href="https://redirect.github.com/Aleph-Alpha/ts-rs/pull/434">#434</a>)</li>
</ul>
</blockquote>
</details>
<details>
<summary>Commits</summary>
<ul>
<li>See full diff in <a
href="https://github.com/Aleph-Alpha/ts-rs/commits/v11.1.0">compare
view</a></li>
</ul>
</details>
<br />


[![Dependabot compatibility
score](https://dependabot-badges.githubapp.com/badges/compatibility_score?dependency-name=ts-rs&package-manager=cargo&previous-version=11.0.1&new-version=11.1.0)](https://docs.github.com/en/github/managing-security-vulnerabilities/about-dependabot-security-updates#about-compatibility-scores)

Dependabot will resolve any conflicts with this PR as long as you don't
alter it yourself. You can also trigger a rebase manually by commenting
`@dependabot rebase`.

[//]: # (dependabot-automerge-start)
[//]: # (dependabot-automerge-end)

---

<details>
<summary>Dependabot commands and options</summary>
<br />

You can trigger Dependabot actions by commenting on this PR:
- `@dependabot rebase` will rebase this PR
- `@dependabot recreate` will recreate this PR, overwriting any edits
that have been made to it
- `@dependabot merge` will merge this PR after your CI passes on it
- `@dependabot squash and merge` will squash and merge this PR after
your CI passes on it
- `@dependabot cancel merge` will cancel a previously requested merge
and block automerging
- `@dependabot reopen` will reopen this PR if it is closed
- `@dependabot close` will close this PR and stop Dependabot recreating
it. You can achieve the same result by closing it manually
- `@dependabot show <dependency name> ignore conditions` will show all
of the ignore conditions of the specified dependency
- `@dependabot ignore this major version` will close this PR and stop
Dependabot creating any more for this major version (unless you reopen
the PR or upgrade to it yourself)
- `@dependabot ignore this minor version` will close this PR and stop
Dependabot creating any more for this minor version (unless you reopen
the PR or upgrade to it yourself)
- `@dependabot ignore this dependency` will close this PR and stop
Dependabot creating any more for this dependency (unless you reopen the
PR or upgrade to it yourself)


</details>

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-12-08 08:55:06 -08:00
Pavel
056c2ee276 fix: update URLs to use HTTPS in model migration prompts (#7705)
Update URLs to use HTTPS in model migration prompts

Closes #6685
2025-12-08 08:47:33 -08:00
gameofby
98923654d0 fix: refine the warning message and docs for deprecated tools config (#7685)
Issue #7661 revealed that users are confused by deprecation warnings
like:
> `tools.web_search` is deprecated. Use `web_search_request` instead.

This message misleadingly suggests renaming the config key from
`web_search` to `web_search_request`, when the actual required change is
to **move and rename the configuration from the `[tools]` section to the
`[features]` section**.

This PR clarifies the warning messages and documentation to make it
clear that deprecated `[tools]` configurations should be moved to
`[features]`. Changes made:
- Updated deprecation warning format in `codex-rs/core/src/codex.rs:520`
to include `[features].` prefix
- Updated corresponding test expectations in
`codex-rs/core/tests/suite/deprecation_notice.rs:39`
- Improved documentation in `docs/config.md` to clarify upfront that
`[tools]` options are deprecated in favor of `[features]`
2025-12-08 01:23:21 -08:00
Robby He
57ba9fa100 fix(doc): TOML otel exporter example — multi-line inline table is inv… (#7669)
…alid (#7668)

The `otel` exporter example in `docs/config.md` is misleading and will
cause
the configuration parser to fail if copied verbatim.

Summary
-------
The example uses a TOML inline table but spreads the inline-table braces
across multiple lines. TOML inline tables must be contained on a single
line
(`key = { a = 1, b = 2 }`); placing newlines inside the braces triggers
a
parse error in most TOML parsers and prevents Codex from starting.

Reproduction
------------
1. Paste the snippet below into `~/.codex/config.toml` (or your project
config).
2. Run `codex` (or the command that loads the config).
3. The process will fail to start with a TOML parse error similar to:

```text
Error loading config.toml: TOML parse error at line 55, column 27
   |
55 | exporter = { otlp-http = {
   |                           ^
newlines are unsupported in inline tables, expected nothing
```

Problematic snippet (as currently shown in the docs)
---------------------------------------------------
```toml
[otel]
exporter = { otlp-http = {
  endpoint = "https://otel.example.com/v1/logs",
  protocol = "binary",
  headers = { "x-otlp-api-key" = "${OTLP_TOKEN}" }
}}
```

Recommended fixes
------------------
```toml
[otel.exporter."otlp-http"]
endpoint = "https://otel.example.com/v1/logs"
protocol = "binary"

[otel.exporter."otlp-http".headers]
"x-otlp-api-key" = "${OTLP_TOKEN}"
```

Or, keep an inline table but write it on one line (valid but less
readable):

```toml
[otel]
exporter = { "otlp-http" = { endpoint = "https://otel.example.com/v1/logs", protocol = "binary", headers = { "x-otlp-api-key" = "${OTLP_TOKEN}" } } }
```
2025-12-08 01:20:23 -08:00
Eric Traut
acb8ed493f Fixed regression for chat endpoint; missing tools name caused litellm proxy to crash (#7724)
This PR addresses https://github.com/openai/codex/issues/7051
2025-12-08 00:49:51 -08:00
Ahmed Ibrahim
53a486f7ea Add remote models feature flag (#7648)
# External (non-OpenAI) Pull Request Requirements

Before opening this Pull Request, please read the dedicated
"Contributing" markdown file or your PR may be closed:
https://github.com/openai/codex/blob/main/docs/contributing.md

If your PR conforms to our contribution guidelines, replace this text
with a detailed and high quality description of your changes.

Include a link to a bug report or enhancement request.
2025-12-07 09:47:48 -08:00
Michael Bolin
3c3d3d1adc fix: add integration tests for codex-exec-mcp-server with execpolicy (#7617)
This PR introduces integration tests that run
[codex-shell-tool-mcp](https://www.npmjs.com/package/@openai/codex-shell-tool-mcp)
as a user would. Note that this requires running our fork of Bash, so we
introduce a [DotSlash](https://dotslash-cli.com/) file for `bash` so
that we can run the integration tests on multiple platforms without
having to check the binaries into the repository. (As noted in the
DotSlash file, it is slightly more heavyweight than necessary, which may
be worth addressing as disk space in CI is limited:
https://github.com/openai/codex/pull/7678.)

To start, this PR adds two tests:

- `list_tools()` makes the `list_tools` request to the MCP server and
verifies we get the expected response
- `accept_elicitation_for_prompt_rule()` defines a `prefix_rule()` with
`decision="prompt"` and verifies the elicitation flow works as expected

Though the `accept_elicitation_for_prompt_rule()` test **only works on
Linux**, as this PR reveals that there are currently issues when running
the Bash fork in a read-only sandbox on Linux. This will have to be
fixed in a follow-up PR.

Incidentally, getting this test run to correctly on macOS also requires
a recent fix we made to `brew` that hasn't hit a mainline release yet,
so getting CI green in this PR required
https://github.com/openai/codex/pull/7680.
2025-12-07 06:39:38 +00:00
Michael Bolin
3c087e8fda fix: ensure macOS CI runners for Rust tests include recent Homebrew fixes (#7680)
As noted in the code comment, we introduced a key fix for `brew` in
https://github.com/Homebrew/brew/pull/21157 that Codex needs, but it has
not hit stable yet, so we update our CI job to use latest `brew` from
`origin/main`.

This is necessary for the new integration tests introduced in
https://github.com/openai/codex/pull/7617.
2025-12-06 22:11:07 -08:00
Michael Bolin
7386e2efbc fix: clear out space on ubuntu runners before running Rust tests (#7678)
When I put up https://github.com/openai/codex/pull/7617 for review,
initially I started seeing failures on the `ubuntu-24.04` runner used
for Rust test runs for the `x86_64-unknown-linux-gnu` architecture. Chat
suggested a number of things that could be removed to save space, which
seems to help.
2025-12-06 21:46:07 -08:00
Victor
b2cb05d562 docs: point dev checks to just (#7673)
Update install and contributing guides to use the root justfile helpers
(`just fmt`, `just fix -p <crate>`, and targeted tests) instead of the
older cargo fmt/clippy/test instructions that have been in place since
459363e17b. This matches the justfile relocation to the repo root in
952d6c946 and the current lint/test workflow for CI (see
`.github/workflows/rust-ci.yml`).
2025-12-06 18:57:08 -08:00
Jay Sabva
9a74228c66 docs: Remove experimental_use_rmcp_client from config (#7672)
Removed experimental Rust MCP client option from config.
2025-12-06 16:51:07 -08:00
Jay Sabva
315b1e957d docs: fix documentation of rmcp client flag (#7665)
## Summary
- Updated the rmcp client flag's documentation in config.md file
- changed it from `experimental_use_rmcp_client` to `rmcp_client`
2025-12-06 10:17:18 -08:00
Michael Bolin
82090803d9 fix: exec-server stream was erroring for large requests (#7654)
Previous to this change, large `EscalateRequest` payloads exceeded the
kernel send buffer, causing our single `sendmsg(2)` call (with attached
FDs) to be split and retried without proper control handling; this led
to `EINVAL`/broken pipe in the
`handle_escalate_session_respects_run_in_sandbox_decision()` test when
using an `env` with large contents.

**Before:** `AsyncSocket::send_with_fds()` called `send_json_message()`,
which called `send_message_bytes()`, which made one `socket.sendmsg()`
call followed by additional `socket.send()` calls, as necessary:


2e4a402521/codex-rs/exec-server/src/posix/socket.rs (L198-L209)

**After:** `AsyncSocket::send_with_fds()` now calls
`send_stream_frame()`, which calls `send_stream_chunk()` one or more
times. Each call to `send_stream_chunk()` calls `socket.sendmsg()`.

In the previous implementation, the subsequent `socket.send()` writes
had no control information associated with them, whereas in the new
`send_stream_chunk()` implementation, a fresh `MsgHdr` (using
`with_control()`, as appropriate) is created for `socket.sendmsg()` each
time.

Additionally, with this PR, stream sending attaches `SCM_RIGHTS` only on
the first chunk, and omits control data when there are no FDs, allowing
oversized payloads to deliver correctly while preserving FD limits and
error checks.
2025-12-06 10:16:47 -08:00
Alexander
f521d29726 fix: OTEL HTTP exporter panic and mTLS support (#7651)
This fixes two issues with the OTEL HTTP exporter:

1. **Runtime panic with async reqwest client**

The `opentelemetry_sdk` `BatchLogProcessor` spawns a dedicated OS thread
that uses `futures_executor::block_on()` rather than tokio's runtime.
When the async reqwest client's timeout mechanism calls
`tokio::time::sleep()`, it panics with "there is no reactor running,
must be called from the context of a Tokio 1.x runtime".

The fix is to use `reqwest::blocking::Client` instead, which doesn't
depend on tokio for timeouts. However, the blocking client creates its
own internal tokio runtime during construction, which would panic if
built from within an async context. We wrap the construction in
`tokio::task::block_in_place()` to handle this.

2. **mTLS certificate handling**

The HTTP client wasn't properly configured for mTLS, matching the fixes
previously done for the model provider client:

- Added `.tls_built_in_root_certs(false)` when using a custom CA
certificate to ensure only our CA is trusted
- Added `.https_only(true)` when using client identity
- Added `rustls-tls` feature to ensure rustls is used (required for
`Identity::from_pem()` to work correctly)
2025-12-05 20:46:44 -08:00
xl-openai
93f61dbc5f Also load skills from repo root. (#7645)
Also load skills from /REPO_ROOT/codex/skills.
2025-12-05 18:01:49 -08:00
Dylan Hurd
6c9c563faf fix(apply-patch): preserve CRLF line endings on Windows (#7515)
## Summary
This PR is heavily based on #4017, which contains the core logic for the
fix. To reduce the risk, we are first introducing it only on windows. We
can then expand to wsl / other environments as needed, and then tackle
net new files.

## Testing
- [x] added unit tests in apply-patch
- [x] add integration tests to apply_patch_cli.rs

---------

Co-authored-by: Chase Naples <Cnaples79@gmail.com>
2025-12-05 16:43:27 -08:00
Josh McKinney
952d6c9465 Move justfile to repository root (#7652)
## Summary
- move the workspace justfile to the repository root for easier
discovery
- set the just working directory to codex-rs so existing recipes still
run in the Rust workspace

## Testing
- not run (not requested)


------
[Codex
Task](https://chatgpt.com/codex/tasks/task_i_69334db473108329b0cc253b7fd8218e)
2025-12-05 16:24:55 -08:00
Jeremy Rose
2e4a402521 cloud: status, diff, apply (#7614)
Adds cli commands for getting the status of cloud tasks, and for
getting/applying the diffs from same.
2025-12-05 21:39:23 +00:00
Pavel Krymets
f48d88067e Fix unified_exec on windows (#7620)
Fix unified_exec on windows

Requires removal of PSUEDOCONSOLE_INHERIT_CURSOR flag so child processed
don't attempt to wait for cursor position response (and timeout).


https://github.com/wezterm/wezterm/compare/main...pakrym:wezterm:PSUEDOCONSOLE_INHERIT_CURSOR?expand=1

---------

Co-authored-by: pakrym-oai <pakrym@openai.com>
2025-12-05 20:09:43 +00:00
Dylan Hurd
a8cbbdbc6e feat(core) Add login to shell_command tool (#6846)
## Summary
Adds the `login` parameter to the `shell_command` tool - optional,
defaults to true.

## Testing
- [x] Tested locally
2025-12-05 11:03:25 -08:00
Ahmed Ibrahim
d08efb1743 Wire with_remote_overrides to construct model families (#7621)
- This PR wires `with_remote_overrides` and make the
`construct_model_families` an async function
- Moves getting model family a level above to keep the function `sync`
- Updates the tests to local, offline, and `sync` helper for model
families
2025-12-05 10:40:15 -08:00
jif-oai
5f80ad6da8 fix: chat completion with parallel tool call (#7634) 2025-12-05 10:20:36 -08:00
jif-oai
e91bb6b947 fix: ignore ghost snapshots in token consumption (#7638) 2025-12-05 13:57:24 +00:00
zhao-oai
b8eab7ce90 fix: taking plan type from usage endpoint instead of thru auth token (#7610)
pull plan type from the usage endpoint, persist it in session state /
tui state, and propagate through rate limit snapshots
2025-12-04 23:34:13 -08:00
zhao-oai
b1c918d8f7 feat: exec policy integration in shell mcp (#7609)
adding execpolicy support into the `posix` mcp

Co-authored-by: Michael Bolin <mbolin@openai.com>
2025-12-04 21:55:54 -08:00
zhao-oai
4c9762d15c fix typo (#7626) 2025-12-04 21:48:15 -08:00
Ahmed Ibrahim
7b359c9c8e Call models endpoint in models manager (#7616)
- Introduce `with_remote_overrides` and update
`refresh_available_models`
- Put `auth_manager` instead of `auth_mode` on `models_manager`
- Remove `ShellType` and `ReasoningLevel` to use already existing
structs
2025-12-04 18:28:03 -08:00
jif-oai
6736d1828d fix: sse for chat (#7594) 2025-12-04 16:46:56 -08:00
Dylan Hurd
073a8533b8 chore(apply-patch) scenarios for e2e testing (#7567)
## Summary
This PR introduces an End to End test suite for apply-patch, so we can
easily validate behavior against other implementations as well.

## Testing
- [x] These are tests
2025-12-05 00:20:54 +00:00
Michael Bolin
0972cd9404 chore: refactor to move Arc<RwLock> concern outside exec_policy_for (#7615)
The caller should decide whether wrapping the policy in `Arc<RwLock>` is
necessary. This should make https://github.com/openai/codex/pull/7609 a
bit smoother.

- `exec_policy_for()` -> `load_exec_policy_for_features()`
- introduce `load_exec_policy()` that does not take `Features` as an arg
- both return `Result<Policy, ExecPolicyError>` instead of
Result<Arc<RwLock<Policy>>, ExecPolicyError>`

This simplifies the tests as they have no need for `Arc<RwLock>`.
2025-12-04 15:13:27 -08:00
Robby He
28dcdb566a Fix handle_shortcut_overlay_key for cross-platform consistency (#7583)
**Summary**
- Shortcut toggle using `?` in `handle_shortcut_overlay_key` fails to
trigger on some platforms (notably Windows). Current match requires
`KeyCode::Char('?')` with `KeyModifiers::NONE`. Some terminals set
`SHIFT` when producing `?` (since it is typically `Shift + /`), so the
strict `NONE` check prevents toggling.

**Impact**
- On Windows consoles/terminals, pressing `?` with an empty composer
often does nothing, leading to inconsistent UX compared to macOS/Linux.

**Root Cause**
- Crossterm/terminal backends report modifiers inconsistently across
platforms. Generating `?` may include `SHIFT`. The code enforces
`modifiers == NONE`, so valid `?` presses with `SHIFT` are ignored.
AltGr keyboards may also surface as `ALT`.

**Repro Steps**
- Open the TUI, ensure the composer is empty.
- Press `?`.
- Expected: Shortcut overlay toggles.
- Actual (Windows frequently): No toggle occurs.

**Fix Options**
- Option 1 (preferred): Accept `?` regardless of `SHIFT`, but reject
`CONTROL` and `ALT`.
- Rationale: Keeps behavior consistent across platforms with minimal
code change.
	- Example change:
		- Before: matching `KeyModifiers::NONE` only.
		- After: allow `SHIFT`, disallow `CONTROL | ALT`.
		- Suggested condition:
			```rust
			let toggles = matches!(key_event.code, KeyCode::Char('?'))
&& !key_event.modifiers.intersects(KeyModifiers::CONTROL |
KeyModifiers::ALT)
					&& self.is_empty();
			```

- Option 2: Platform-specific handling (Windows vs non-Windows).
- Implement two variants or conditional branches using `#[cfg(target_os
= "windows")]`.
- On Windows, accept `?` with `SHIFT`; on other platforms, retain
current behavior.
- Trade-off: Higher maintenance burden and code divergence for limited
benefit.

---

close #5495
2025-12-04 14:56:58 -08:00
Owen Lin
e8f6d65899 fix(app-server): add will_retry to ErrorNotification (#7611)
VSCE renders `codex/event/stream_error` (automatically retried, e.g.
`"Reconnecting... 1/n"`) and `codex/event/error` (terminal errors)
differently, so add `will_retry` on ErrorNotification to indicate this.
2025-12-04 21:48:37 +00:00
Owen Lin
342c084cc3 fix(app-server): add duration_ms to McpToolCallItem (#7605)
Seems like a nice field to have, and also VSCE does render this one.
2025-12-04 13:45:07 -08:00
Ahmed Ibrahim
903b7774bc Add models endpoint (#7603)
- Use the codex-api crate to introduce models endpoint. 
- Add `models` to codex core tests helpers
- Add `ModelsInfo` for the endpoint return type
2025-12-04 12:57:54 -08:00
Ahmed Ibrahim
6e6338aa87 Inline response recording and remove process_items indirection (#7310)
- Inline response recording during streaming: `run_turn` now records
items as they arrive instead of building a `ProcessedResponseItem` list
and post‑processing via `process_items`.
- Simplify turn handling: `handle_output_item_done` returns the
follow‑up signal + optional tool future; `needs_follow_up` is set only
there, and in‑flight tool futures are drained once at the end (errors
logged, no extra state writes).
- Flattened stream loop: removed `process_items` indirection and the
extra output queue
- - Tests: relaxed `tool_parallelism::tool_results_grouped` to allow any
completion order while still requiring matching call/output IDs.
2025-12-04 12:17:54 -08:00
Jeremy Rose
7dfc3a4dc7 add --branch to codex cloud exec (#7602)
Adds `--branch` to `codex cloud exec` to set base branch.
2025-12-04 12:00:18 -08:00
Ahmed Ibrahim
9b2055586d remove model_family from `config (#7571)
- Remove `model_family` from `config`
- Make sure to still override config elements related to `model_family`
like supporting reasoning
2025-12-04 11:57:58 -08:00
Maxime Savard
ce0b38c056 FIX: WSL Paste image does not work (#6793)
## Related issues:  
- https://github.com/openai/codex/issues/3939  
- https://github.com/openai/codex/issues/2292  
- https://github.com/openai/codex/issues/7528 (After correction
https://github.com/openai/codex/pull/3990)

**Area:** `codex-cli` (image handling / clipboard & file uploads)  
**Platforms affected:** WSL (Ubuntu on Windows 10/11). No behavior
change on native Linux/macOS/Windows.

## Summary

This PR fixes image pasting and file uploads when running `codex-cli`
inside WSL. Previously, image operations failed silently or with
permission errors because paths weren't properly mapped between Windows
and WSL filesystems.

## Visual Result

<img width="1118" height="798" alt="image"
src="https://github.com/user-attachments/assets/14e10bc4-6b71-4d1f-b2a6-52c0a67dd069"
/>

## Last Rust-Cli

<img width="1175" height="859" alt="image"
src="https://github.com/user-attachments/assets/7ef41e29-9118-42c9-903c-7116d21e1751"
/>

## Root cause

The CLI assumed native Linux/Windows environments and didn't handle the
WSL↔Windows boundary:

- Used Linux paths for files that lived on the Windows host
- Missing path normalization between Windows (`C:\...`) and WSL
(`/mnt/c/...`)
- Clipboard access failed under WSL

### Why `Ctrl+V` doesn't work in WSL terminals

Most WSL terminal emulators (Windows Terminal, ConEmu, etc.) intercept
`Ctrl+V` at the terminal level to paste text from the Windows clipboard.
This keypress never reaches the CLI application itself, so our clipboard
image handler never gets triggered. Users need `Ctrl+Alt+V`.

## Changes

### WSL detection & path mapping

- Detects WSL by checking `/proc/sys/kernel/osrelease` and the
`WSL_INTEROP` env var
- Maps Windows drive paths to WSL mount paths (`C:\...` → `/mnt/c/...`)

### Clipboard fallback for WSL

- When clipboard access fails under WSL, falls back to PowerShell to
extract images from the Windows clipboard
- Saves to a temp file and maps the path back to WSL

### UI improvements

- Shows `Ctrl+Alt+V` hint on WSL (many terminals intercept plain
`Ctrl+V`)
- Better error messages for unreadable images

## Performance

- Negligible overhead. The fallback adds a single FS copy to a temp file
only when needed.
- Direct streaming remains the default.

## Files changed

- `protocol/src/lib.rs` – Added platform detection module  
- `protocol/src/models.rs` – Added WSL path mapping for local images  
- `protocol/src/platform.rs` – New module with WSL detection utilities  
- `tui/src/bottom_pane/chat_composer.rs` – Added base64 data URL support
and WSL path mapping
- `tui/src/bottom_pane/footer.rs` – WSL-aware keyboard shortcuts  
- `tui/src/clipboard_paste.rs` – PowerShell clipboard fallback

## How to reproduce the original bug (pre-fix)

1. Run `codex-cli` inside WSL2 on Windows.  
2. Paste an image from the Windows clipboard or drag an image from
`C:\...` into the terminal.
3. Observe that the image is not attached (silent failure) or an error
is logged; no artifact reaches the tool.

## How to verify the fix

1. Build this branch and run `codex-cli` inside WSL2.  
2. Paste from clipboard and drag from both Windows and WSL paths.  
3. Confirm that the image appears in the tool and the CLI shows a single
concise info line (no warning unless fallback was used).

I’m happy to adjust paths, naming, or split helpers into a separate
module if you prefer.

## How to try this branch

If you want to try this before it’s merged, you can use my Git branch:

Repository: https://github.com/Waxime64/codex.git  
Branch: `wsl-image-2`

1. Start WSL on your Windows machine.
2. Clone the repository and switch to the branch:
   ```bash
   git clone https://github.com/Waxime64/codex.git
   cd codex
   git checkout wsl-image-2
   # then go into the Rust workspace root, e.g.:
   cd codex-rs
3. Build the TUI binary:
  cargo build -p codex-tui --bin codex-tui --release
4. Install the binary:
   sudo install -m 0755 target/release/codex-tui /usr/local/bin/codex
5. From the project directory where you want to use Codex, start it
with:
   cd /path/to/your/project
   /usr/local/bin/codex

On WSL, use CTRL+ALT+V to paste an image from the Windows clipboard into
the chat.
2025-12-04 10:50:20 -08:00
Dylan Hurd
37c36024c7 chore(core): test apply_patch_cli on Windows (#7554)
## Summary
These tests pass on windows, let's enable them.

## Testing
- [x] These are more tests
2025-12-04 10:39:45 -08:00
jif-oai
291b54a762 chore: review in read-only (#7593) 2025-12-04 10:01:12 -08:00
jif-oai
2b5d0b2935 feat: update sandbox policy to allow TTY (#7580)
**Change**: Seatbelt now allows file-ioctl on /dev/ttys[0-9]+ even
without the sandbox extension so pre-created PTYs remain interactive
(Python REPL, shells).

**Risk**: A seatbelted process that already holds a PTY fd (including
one it shouldn’t) could issue tty ioctls like TIOCSTI or termios changes
on that fd. This doesn’t allow opening new PTYs or reading/writing them;
it only broadens ioctl capability on existing fds.

**Why acceptable**: We already hand the child its PTY for interactive
use; restoring ioctls is required for isatty() and prompts to work. The
attack requires being given or inheriting a sensitive PTY fd; by design
we don’t hand untrusted processes other users’ PTYs (we don't hand them
any PTYs actually), so the practical exposure is limited to the PTY
intentionally allocated for the session.

**Validation**:
Running
```
start a python interpreter and keep it running
```
Followed by:
* `calculate 1+1 using it` -> works as expected
* `Use this Python session to run the command just fix in
/Users/jif/code/codex/codex-rs` -> does not work as expected
2025-12-04 17:58:58 +00:00
zhao-oai
404a1ea34b Update execpolicy.md (#7595) 2025-12-04 17:55:42 +00:00
jif-oai
36edb412b1 fix: release session ID when not used (#7592) 2025-12-04 17:42:16 +00:00
jif-oai
1b2509f05a chore: default warning messages to true (#7588) 2025-12-04 17:29:23 +00:00
pakrym-oai
f1b7cdc3bd Use shared check sandboxing (#7547) 2025-12-04 08:34:09 -08:00
pakrym-oai
c4e18f1b63 Slightly better status display for unified exec (#7563)
Trim bash -lc
2025-12-04 08:32:54 -08:00
jif-oai
8f4e00e1f1 chore: tool tip for /prompt (#7591) 2025-12-04 15:13:49 +00:00
zhao-oai
87666695ba execpolicy tui flow (#7543)
## Updating the `execpolicy` TUI flow

In the TUI, when going through the command approval flow, codex will now
ask the user if they would like to whitelist the FIRST unmatched command
among a chain of commands.

For example, let's say the agent wants to run `apple | pear` with an
empty `execpolicy`

Neither apple nor pear will match to an `execpolicy` rule. Thus, when
prompting the user, codex tui will ask the user if they would like to
whitelist `apple`.

If the agent wants to run `apple | pear` again, they would be prompted
again because pear is still unknown. when prompted, the user will now be
asked if they'd like to whitelist `pear`.

Here's a demo video of this flow:


https://github.com/user-attachments/assets/fd160717-f6cb-46b0-9f4a-f0a974d4e710

This PR also removed the `allow for this session` option from the TUI.
2025-12-04 07:58:13 +00:00
ae
871f44f385 Add Enterprise plan to ChatGPT login description (#6918)
## Summary
- update ChatGPT onboarding login description to mention Enterprise
plans alongside Plus, Pro, and Team

## Testing
- just fmt


------
[Codex
Task](https://chatgpt.com/codex/tasks/task_i_691e088daf20832c88d8b667adf45128)
2025-12-03 23:47:46 -08:00
zhao-oai
3d35cb4619 Refactor execpolicy fallback evaluation (#7544)
## Refactor of the `execpolicy` crate

To illustrate why we need this refactor, consider an agent attempting to
run `apple | rm -rf ./`. Suppose `apple` is allowed by `execpolicy`.
Before this PR, `execpolicy` would consider `apple` and `pear` and only
render one rule match: `Allow`. We would skip any heuristics checks on
`rm -rf ./` and immediately approve `apple | rm -rf ./` to run.

To fix this, we now thread a `fallback` evaluation function into
`execpolicy` that runs when no `execpolicy` rules match a given command.
In our example, we would run `fallback` on `rm -rf ./` and prevent
`apple | rm -rf ./` from being run without approval.
2025-12-03 23:39:48 -08:00
zhao-oai
e925a380dc whitelist command prefix integration in core and tui (#7033)
this PR enables TUI to approve commands and add their prefixes to an
allowlist:
<img width="708" height="605" alt="Screenshot 2025-11-21 at 4 18 07 PM"
src="https://github.com/user-attachments/assets/56a19893-4553-4770-a881-becf79eeda32"
/>

note: we only show the option to whitelist the command when 
1) command is not multi-part (e.g `git add -A && git commit -m 'hello
world'`)
2) command is not already matched by an existing rule
2025-12-03 23:17:02 -08:00
Jeremy Rose
ccdeb9d9c4 use markdown for rendering tips (#7557)
## Summary
- render tooltip content through the markdown renderer and prepend a
bold Tip label
- wrap tooltips at the available width using the indent’s measured width
before adding the indent

## Testing
- `/root/.cargo/bin/just fmt`
- `RUSTFLAGS="--cfg tokio_unstable" TOKIO_UNSTABLE=1
/root/.cargo/bin/just fix -p codex-tui` *(fails: codex-tui tests
reference tokio::time::advance/start_paused gated behind the tokio
test-util feature)*
- `RUSTFLAGS="--cfg tokio_unstable" TOKIO_UNSTABLE=1 cargo test -p
codex-tui` *(fails: codex-tui tests reference
tokio::time::advance/start_paused gated behind the tokio test-util
feature)*

------
[Codex
Task](https://chatgpt.com/codex/tasks/task_i_693081406050832c9772ae9fa5dd77ca)
2025-12-04 04:58:35 +00:00
Ahmed Ibrahim
67e67e054f Migrate codex max (#7566)
- make codex max the default
- fix: we were doing some async work in sync function which caused tui
to panic
2025-12-03 20:54:48 -08:00
Eric Traut
edd98dd3b7 Remove test from #7481 that doesn't add much value (#7558)
Follow-up from PR #7481
2025-12-03 19:10:54 -08:00
Celia Chen
3e6cd5660c [app-server] make file_path for config optional (#7560)
When we are writing to config using `config/value/write` or
`config/batchWrite`, it always require a `config/read` before it right
now in order to get the correct file path to write to. make this
optional so we read from the default user config file if this is not
passed in.
2025-12-04 03:08:18 +00:00
Ahmed Ibrahim
cee37a32b2 Migrate model family to models manager (#7565)
This PR moves `ModelsFamily` to `openai_models`. It also propagates
`ModelsManager` to session services and use it to drive model family. We
also make `derive_default_model_family` private because it's a step
towards what we want: one place that gives model configuration.

This is a second step at having one source of truth for models
information and config: `ModelsManager`.

Next steps would be to remove `ModelsFamily` from config. That's massive
because it's being used in 41 occasions mostly pre launching `codex`.
Also, we need to make `find_family_for_model` private. It's also big
because it's being used in 21 occasions ~ all tests.
2025-12-03 18:49:47 -08:00
Ahmed Ibrahim
8da91d1c89 Migrate tui to use models manager (#7555)
- This PR treats the `ModelsManager` like `AuthManager` and propagate it
into the tui, replacing the `builtin_model_presets`
- We are also decreasing the visibility of `builtin_model_presets`

based on https://github.com/openai/codex/pull/7552
2025-12-03 18:00:47 -08:00
Ahmed Ibrahim
00cc00ead8 Introduce ModelsManager and migrate app-server to use it. (#7552) 2025-12-03 17:17:56 -08:00
muyuanjin
70b97790be fix: wrap long exec lines in transcript overlay (#7481)
What
-----
- Fix the Ctrl+T transcript overlay so that very long exec output lines
are soft‑wrapped to the viewport width instead of being rendered as a
single truncated row.
- Add a regression test to `TranscriptOverlay` to ensure long exec
outputs are rendered on multiple lines in the overlay.

Why
----
- Previously, the transcript overlay rendered extremely long single exec
lines as one on‑screen row and simply cut them off at the right edge,
with no horizontal scrolling.
- This made it impossible to inspect the full content of long tool/exec
outputs in the transcript view, even though the main TUI view already
wrapped those lines.
- Fixes #7454.

How
----
- Update `ExecCell::transcript_lines` to wrap exec output lines using
the existing `RtOptions`/`word_wrap_line` helpers so that transcript
rendering is width‑aware.
- Reuse the existing line utilities to expand the wrapped `Line` values
into the transcript overlay, preserving styling while respecting the
current viewport width.
- Add `transcript_overlay_wraps_long_exec_output_lines` test in
`pager_overlay.rs` that constructs a long single‑line exec output,
renders the transcript overlay into a small buffer, and asserts that the
long marker string spans multiple rendered lines.
2025-12-03 16:45:08 -08:00
Michael Bolin
1cfc967eb8 fix: Features should be immutable over the lifetime of a session/thread (#7540)
I noticed that `features: Features` was defined on `struct
SessionConfiguration`, which is commonly owned by `SessionState`, which
is in turn owned by `Session`.

Though I do not believe that `Features` should be allowed to be modified
over the course of a session (if the feature state is not invariant, it
makes it harder to reason about), which argues that it should live on
`Session` rather than `SessionState` or `SessionConfiguration`.

This PR moves `Features` to `Session` and updates all call sites. It
appears the only place we were mutating `Features` was:

- in tests
- the sub-agent config for a review task:


3ef76ff29d/codex-rs/core/src/tasks/review.rs (L86-L89)

Note this change also means it is no longer an `async` call to check the
state of a feature, eliminating the possibility of a
[TOCTTOU](https://en.wikipedia.org/wiki/Time-of-check_to_time-of-use)
error between checking the state of a feature and acting on it:


3ef76ff29d/codex-rs/core/src/codex.rs (L1069-L1076)
2025-12-03 16:12:31 -08:00
xl-openai
9a50a04400 feat: Support listing and selecting skills via $ or /skills (#7506)
List/Select skills with $-mention or /skills
2025-12-03 15:12:46 -08:00
Owen Lin
231ff19ca2 [app-server] fix: add thread_id to turn/plan/updated (#7553)
Realized we're missing this while migrating VSCE.
2025-12-03 15:00:07 -08:00
Aofei Sheng
de08c735a6 feat(tui): map Ctrl-P/N to arrow navigation in textarea (#7530)
- Treat Ctrl-P/N (and their C0 fallbacks) the same as Up/Down so cursor
movement matches popup/history behavior and control bytes never land in
the buffer

Fixes #7529

Signed-off-by: Aofei Sheng <aofei@aofeisheng.com>
2025-12-03 14:43:31 -08:00
muyuanjin
3395ebd96e fix(tui): limit user shell output by screen lines (#7448)
What
- Limit the TUI "user shell" output panel by the number of visible
screen lines rather than by the number of logical lines.
- Apply middle truncation after wrapping, so a few extremely long lines
cannot expand into hundreds of visible lines.
- Add a regression test to guard this behavior.

Why
When the `ExecCommandSource::UserShell` tool returns a small number of
very long logical lines, the TUI wraps those lines into many visual
lines. The existing truncation logic applied
`USER_SHELL_TOOL_CALL_MAX_LINES` to the number of logical lines *before*
wrapping.

As a result, a command like:

- `Ran bash -lc "grep -R --line-number 'maskAssetId' ."`

or a synthetic command that prints a single ~50,000‑character line, can
produce hundreds of screen lines and effectively flood the viewport. The
intended middle truncation for user shell output does not take effect in
this scenario.

How
- In `codex-rs/tui/src/exec_cell/render.rs`, change the `ExecCell`
rendering path for `ExecCommandSource::UserShell` so that:
- Each logical line from `CommandOutput::aggregated_output` is first
wrapped via `word_wrap_line` into multiple screen lines using the
appropriate `RtOptions` and width from the `EXEC_DISPLAY_LAYOUT`
configuration.
- `truncate_lines_middle` is then applied to the wrapped screen lines,
with `USER_SHELL_TOOL_CALL_MAX_LINES` as the limit. This means the limit
is enforced on visible screen lines, not logical lines.
- The existing layout struct (`ExecDisplayLayout`) continues to provide
`output_max_lines`, so user shell output is subject to both
`USER_SHELL_TOOL_CALL_MAX_LINES` and the layout-specific
`output_max_lines` constraint.
- Keep using `USER_SHELL_TOOL_CALL_MAX_LINES` as the cap, but interpret
it as a per‑tool‑call limit on screen lines.
- Add a regression test `user_shell_output_is_limited_by_screen_lines`
in `codex-rs/tui/src/exec_cell/render.rs` that:
- Constructs two extremely long logical lines containing a short marker
(`"Z"`), so each wrapped screen line still contains the marker.
  - Wraps them at a narrow width to generate many screen lines.
- Asserts that the unbounded wrapped output would exceed
`USER_SHELL_TOOL_CALL_MAX_LINES` screen lines.
- Renders an `ExecCell` for `ExecCommandSource::UserShell` at the same
width and counts rendered lines containing the marker.
- Asserts `output_screen_lines <= USER_SHELL_TOOL_CALL_MAX_LINES`,
guarding against regressions where truncation happens before wrapping.

This change keeps user shell output readable while ensuring it cannot
flood the TUI, even when the tool emits a few extremely long lines.

Tests
- `cargo test -p codex-tui`

Issue
- Fixes #7447
2025-12-03 13:43:17 -08:00
Ahmed Ibrahim
71504325d3 Migrate model preset (#7542)
- Introduce `openai_models` in `/core`
- Move `PRESETS` under it
- Move `ModelPreset`, `ModelUpgrade`, `ReasoningEffortPreset`,
`ReasoningEffortPreset`, and `ReasoningEffortPreset` to `protocol`
- Introduce `Op::ListModels` and `EventMsg::AvailableModels`

Next steps:
- migrate `app-server` and `tui` to use the introduced Operation
2025-12-03 20:30:43 +00:00
jif-oai
7f068cfbcc fix: main (#7546) 2025-12-03 20:15:12 +00:00
jif-oai
9e6c2c1e64 feat: add pycache to excluded directories (#7545) 2025-12-03 20:06:55 +00:00
jif-oai
8d0f023fa9 chore: update unified exec sandboxing detection (#7541)
No integration test for now because it would make them flaky. Tracking
it in my todos to add some once we have a clock based system for
integration tests
2025-12-03 20:06:47 +00:00
Ahmed Ibrahim
2ad980abf4 add slash resume (#7302)
`codex resume` isn't that discoverable. Adding it to the slash commands
can help
2025-12-03 11:25:44 -08:00
Owen Lin
3ef76ff29d chore: conversation_id -> thread_id in app-server feedback/upload (#7538)
Use `thread_id: Option<String>` instead of `conversation_id:
Option<ConversationId>` to be consistent with the rest of app-server v2
APIs.
2025-12-03 18:47:35 +00:00
Owen Lin
844de19561 chore: delete unused TodoList item from app-server (#7537)
This item is sent as a turn notification instead: `turn/plan/updated`,
similar to Turn diffs (which is `turn/diff/updated`).

We treat these concepts as ephemeral compared to Items which are usually
persisted.
2025-12-03 18:47:12 +00:00
Owen Lin
343aa35db1 chore: update app-server README (#7510)
Just keeping the README up to date.

- Reorganize structure a bit to read more naturally
- Update RPC methods
- Update events
2025-12-03 10:41:38 -08:00
Shijie Rao
c3e4f920b4 chore: remove bun env var detect (#7534)
### Summary


[Thread](https://openai.slack.com/archives/C08JZTV654K/p1764780129457519)

We were a bit aggressive on assuming package installer based on env
variables for BUN. Here we are removing those checks.
2025-12-03 10:23:45 -08:00
Shijie Rao
4785344c9c feat: support list mcp servers in app server (#7505)
### Summary
Added `mcp/servers/list` which is equivalent to `/mcp` slash command in
CLI for response. This will be used in VSCE MCP settings to show log in
status, available tools etc.
2025-12-03 09:51:46 -08:00
Jeremy Rose
9b3251f28f seatbelt: allow openpty() (#7507)
This allows `openpty(3)` to run in the default sandbox. Also permit
reading `kern.argmax`, which is the maximum number of arguments to
exec().
2025-12-03 09:15:38 -08:00
jif-oai
45f3250eec feat: codex tool tips (#7440)
<img width="551" height="316" alt="Screenshot 2025-12-01 at 12 22 26"
src="https://github.com/user-attachments/assets/6ca3deff-8ef8-4f74-a8e1-e5ea13fd6740"
/>
2025-12-03 16:29:13 +00:00
jif-oai
51307eaf07 feat: retroactive image placeholder to prevent poisoning (#6774)
If an image can't be read by the API, it will poison the entire history,
preventing any new turn on the conversation.
This detect such cases and replace the image by a placeholder
2025-12-03 11:35:56 +00:00
jif-oai
42ae738f67 feat: model warning in case of apply patch (#7494) 2025-12-03 09:07:31 +00:00
Dylan Hurd
00ef9d3784 fix(tui) Support image paste from clipboard on native Windows (#7514)
Closes #3404 

## Summary
On windows, ctrl+v does not work for the same reason that cmd+v does not
work on macos. This PR adds alt/option+v detection, which allows windows
users to paste images from the clipboard using.

We could swap between just ctrl on mac and just alt on windows, but this
felt simpler - I don't feel strongly about it.

Note that this will NOT address image pasting in WSL environments, due
to issues with WSL <> Windows clipboards. I'm planning to address that
in a separate PR since it will likely warrant some discussion.

## Testing
- [x] Tested locally on a Mac and Windows laptop
2025-12-02 22:12:49 -08:00
Robby He
f3989f6092 fix(unified_exec): use platform default shell when unified_exec shell… (#7486)
# Unified Exec Shell Selection on Windows

## Problem

reference issue #7466

The `unified_exec` handler currently deserializes model-provided tool
calls into the `ExecCommandArgs` struct:

```rust
#[derive(Debug, Deserialize)]
struct ExecCommandArgs {
    cmd: String,
    #[serde(default)]
    workdir: Option<String>,
    #[serde(default = "default_shell")]
    shell: String,
    #[serde(default = "default_login")]
    login: bool,
    #[serde(default = "default_exec_yield_time_ms")]
    yield_time_ms: u64,
    #[serde(default)]
    max_output_tokens: Option<usize>,
    #[serde(default)]
    with_escalated_permissions: Option<bool>,
    #[serde(default)]
    justification: Option<String>,
}
```

The `shell` field uses a hard-coded default:

```rust
fn default_shell() -> String {
    "/bin/bash".to_string()
}
```

When the model returns a tool call JSON that only contains `cmd` (which
is the common case), Serde fills in `shell` with this default value.
Later, `get_command` uses that value as if it were a model-provided
shell path:

```rust
fn get_command(args: &ExecCommandArgs) -> Vec<String> {
    let shell = get_shell_by_model_provided_path(&PathBuf::from(args.shell.clone()));
    shell.derive_exec_args(&args.cmd, args.login)
}
```

On Unix, this usually resolves to `/bin/bash` and works as expected.
However, on Windows this behavior is problematic:

- The hard-coded `"/bin/bash"` is not a valid Windows path.
- `get_shell_by_model_provided_path` treats this as a model-specified
shell, and tries to resolve it (e.g. via `which::which("bash")`), which
may or may not exist and may not behave as intended.
- In practice, this leads to commands being executed under a non-default
or non-existent shell on Windows (for example, WSL bash), instead of the
expected Windows PowerShell or `cmd.exe`.

The core of the issue is that **"model did not specify `shell`" is
currently interpreted as "the model explicitly requested `/bin/bash`"**,
which is both Unix-specific and wrong on Windows.

## Proposed Solution

Instead of hard-coding `"/bin/bash"` into `ExecCommandArgs`, we should
distinguish between:

1. **The model explicitly specifying a shell**, e.g.:

   ```json
   {
     "cmd": "echo hello",
     "shell": "pwsh"
   }
   ```

In this case, we *do* want to respect the model’s choice and use
`get_shell_by_model_provided_path`.

2. **The model omitting the `shell` field entirely**, e.g.:

   ```json
   {
     "cmd": "echo hello"
   }
   ```

In this case, we should *not* assume `/bin/bash`. Instead, we should use
`default_user_shell()` and let the platform decide.

To express this distinction, we can:

1. Change `shell` to be optional in `ExecCommandArgs`:

   ```rust
   #[derive(Debug, Deserialize)]
   struct ExecCommandArgs {
       cmd: String,
       #[serde(default)]
       workdir: Option<String>,
       #[serde(default)]
       shell: Option<String>,
       #[serde(default = "default_login")]
       login: bool,
       #[serde(default = "default_exec_yield_time_ms")]
       yield_time_ms: u64,
       #[serde(default)]
       max_output_tokens: Option<usize>,
       #[serde(default)]
       with_escalated_permissions: Option<bool>,
       #[serde(default)]
       justification: Option<String>,
   }
   ```

Here, the absence of `shell` in the JSON is represented as `shell:
None`, rather than a hard-coded string value.
2025-12-02 21:49:25 -08:00
Matthew Zeng
dbec741ef0 Update device code auth strings. (#7498)
- [x] Update device code auth strings.
2025-12-02 17:36:38 -08:00
Michael Bolin
06e7667d0e fix: inline function marked as dead code (#7508)
I was debugging something else and noticed we could eliminate an
instance of `#[allow(dead_code)]` pretty easily.
2025-12-03 00:50:34 +00:00
Ahmed Ibrahim
1ef1fe67ec improve resume performance (#7303)
Reading the tail can be costly if we have a very big rollout item. we
can just read the file metadata
2025-12-02 16:39:40 -08:00
Michael Bolin
ee191dbe81 fix: path resolution bug in npx (#7134)
When running `npx @openai/codex-shell-tool-mcp`, the old code derived
`__dirname` from `process.argv[1]`, which points to npx’s transient
wrapper script in
`~/.npm/_npx/134d0fb7e1a27652/node_modules/.bin/codex-shell-tool-mcp`.
That made `vendorRoot` resolve to `<npx cache>/vendor`, so the startup
checks failed with "Required binary missing" because it looked for
`codex-execve-wrapper` in the wrong place.

By relying on the real module `__dirname` and `path.resolve(__dirname,
"..", "vendor")`, the package now anchors to its installed location
under `node_modules/@openai/codex-shell-tool-mcp/`, so the bundled
binaries are found and npx launches correctly.
2025-12-02 16:37:14 -08:00
Joshua Sutton
ad9eeeb287 Ensure duplicate-length paste placeholders stay distinct (#7431)
Fix issue #7430 
Generate unique numbered placeholders for multiple large pastes of the
same length so deleting one no longer removes the others.

Signed-off-by: Joshua <joshua1s@protonmail.com>
2025-12-02 16:16:01 -08:00
Michael Bolin
6b5b9a687e feat: support --version flag for @openai/codex-shell-tool-mcp (#7504)
I find it helpful to easily verify which version is running.

Tested:

```shell
~/code/codex3/codex-rs/exec-server$ cargo run --bin codex-exec-mcp-server -- --help
    Finished `dev` profile [unoptimized + debuginfo] target(s) in 0.19s
     Running `/Users/mbolin/code/codex3/codex-rs/target/debug/codex-exec-mcp-server --help`
Usage: codex-exec-mcp-server [OPTIONS]

Options:
      --execve <EXECVE_WRAPPER>  Executable to delegate execve(2) calls to in Bash
      --bash <BASH_PATH>         Path to Bash that has been patched to support execve() wrapping
  -h, --help                     Print help
  -V, --version                  Print version
~/code/codex3/codex-rs/exec-server$ cargo run --bin codex-exec-mcp-server -- --version
    Finished `dev` profile [unoptimized + debuginfo] target(s) in 0.17s
     Running `/Users/mbolin/code/codex3/codex-rs/target/debug/codex-exec-mcp-server --version`
codex-exec-server 0.0.0
```
2025-12-02 23:43:25 +00:00
Josh McKinney
58e1e570fa refactor: tui.rs extract several pieces (#7461)
Pull FrameRequester out of tui.rs into its own module and make a
FrameScheduler struct. This is effectively an Actor/Handler approach
(see https://ryhl.io/blog/actors-with-tokio/). Adds tests and docs.

Small refactor of pending_viewport_area logic.
2025-12-02 15:19:27 -08:00
Michael Bolin
ec93b6daf3 chore: make create_approval_requirement_for_command an async fn (#7501)
I think this might help with https://github.com/openai/codex/pull/7033
because `create_approval_requirement_for_command()` will soon need
access to `Session.state`, which is a `tokio::sync::Mutex` that needs to
be accessed via `async`.
2025-12-02 15:01:15 -08:00
liam
4d4778ec1c Trim history.jsonl when history.max_bytes is set (#6242)
This PR honors the `history.max_bytes` configuration parameter by
trimming `history.jsonl` whenever it grows past the configured limit.
While appending new entries we retain the newest record, drop the oldest
lines to stay within the byte budget, and serialize the compacted file
back to disk under the same lock to keep writers safe.
2025-12-02 14:01:05 -08:00
Owen Lin
77c457121e fix: remove serde(flatten) annotation for TurnError (#7499)
The problem with using `serde(flatten)` on Turn status is that it
conditionally serializes the `error` field, which is not the pattern we
want in API v2 where all fields on an object should always be returned.

```
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)]
#[serde(rename_all = "camelCase")]
#[ts(export_to = "v2/")]
pub struct Turn {
    pub id: String,
    /// Only populated on a `thread/resume` response.
    /// For all other responses and notifications returning a Turn,
    /// the items field will be an empty list.
    pub items: Vec<ThreadItem>,
    #[serde(flatten)]
    pub status: TurnStatus,
}

#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)]
#[serde(tag = "status", rename_all = "camelCase")]
#[ts(tag = "status", export_to = "v2/")]
pub enum TurnStatus {
    Completed,
    Interrupted,
    Failed { error: TurnError },
    InProgress,
}
```

serializes to:
```
{
  "id": "turn-123",
  "items": [],
  "status": "completed"
}

{
  "id": "turn-123",
  "items": [],
  "status": "failed",
  "error": {
    "message": "Tool timeout",
    "codexErrorInfo": null
  }
}
```

Instead we want:
```
{
  "id": "turn-123",
  "items": [],
  "status": "completed",
  "error": null
}

{
  "id": "turn-123",
  "items": [],
  "status": "failed",
  "error": {
    "message": "Tool timeout",
    "codexErrorInfo": null
  }
}
```
2025-12-02 21:39:10 +00:00
zhao-oai
5ebdc9af1b persisting credits if new snapshot does not contain credit info (#7490)
in response to incoming changes to responses headers where the header
may sometimes not contain credits info (no longer forcing a credit
check)
2025-12-02 16:23:24 -05:00
Michael Bolin
f6a7da4ac3 fix: drop lock once it is no longer needed (#7500)
I noticed this while doing a post-commit review of https://github.com/openai/codex/pull/7467.
2025-12-02 20:46:26 +00:00
zhao-oai
1d09ac89a1 execpolicy helpers (#7032)
this PR 
- adds a helper function to amend `.codexpolicy` files with new prefix
rules
- adds a utility to `Policy` allowing prefix rules to be added to
existing `Policy` structs

both additions will be helpful as we thread codexpolicy into the TUI
workflow
2025-12-02 15:05:27 -05:00
Ahmed Ibrahim
127e307f89 Show token used when context window is unknown (#7497)
- Show context window usage in tokens instead of percentage when the
window length is unknown.
2025-12-02 11:45:50 -08:00
Ahmed Ibrahim
21ad1c1c90 Use non-blocking mutex (#7467) 2025-12-02 10:50:46 -08:00
lionel-oai
349734e38d Fix: track only untracked paths in ghost snapshots (#7470)
# Ghost snapshot ignores

This PR should close #7067, #7395, #7405.

Prior to this change the ghost snapshot task ran `git status
--ignored=matching` so the report picked up literally every ignored
file. When a directory only contained entries matched by patterns such
as `dozens/*.txt`, `/test123/generated/*.html`, or `/wp-includes/*`, Git
still enumerated them and the large-untracked-dir detection treated the
parent directory as “large,” even though everything inside was
intentionally ignored.

By removing `--ignored=matching` we only capture true untracked paths
now, so those patterns stay out of the snapshot report and no longer
trigger the “large untracked directories” warning.

---------

Signed-off-by: lionelchg <lionel.cheng@hotmail.fr>
Co-authored-by: lionelchg <lionel.cheng@hotmail.fr>
2025-12-02 19:42:33 +01:00
jif-oai
2222cab9ea feat: ignore standard directories (#7483) 2025-12-02 18:42:07 +00:00
Owen Lin
c2f8c4e9f4 fix: add ts number annotations for app-server v2 types (#7492)
These will be more ergonomic to work with in Typescript.
2025-12-02 18:09:41 +00:00
jif-oai
72b95db12f feat: intercept apply_patch for unified_exec (#7446) 2025-12-02 17:54:02 +00:00
Owen Lin
37ee6bf2c3 chore: remove mention of experimental/unstable from app-server README (#7474) 2025-12-02 17:35:05 +00:00
pakrym-oai
8b1e397211 Add request logging back (#7471)
Having full requests helps debugging
2025-12-02 07:57:55 -08:00
jif-oai
85e687c74a feat: add one off commands to app-server v2 (#7452) 2025-12-02 11:56:09 +00:00
jif-oai
9ee855ec57 feat: add warning message for the model (#7445)
Add a warning message as a user turn to the model if the model does not
behave as expected (here, for example, if the model opens too many
`unified_exec` sessions)
2025-12-02 11:56:00 +00:00
jif-oai
4b78e2ab09 chore: review everywhere (#7444) 2025-12-02 11:26:27 +00:00
jif-oai
85e2fabc9f feat: alias compaction (#7442) 2025-12-02 09:21:30 +00:00
Thibault Sottiaux
a8d5ad37b8 feat: experimental support for skills.md (#7412)
This change prototypes support for Skills with the CLI. This is an
**experimental** feature for internal testing.

---------

Co-authored-by: Gav Verma <gverma@openai.com>
2025-12-01 20:22:35 -08:00
Manoel Calixto
32e4a3a4d7 fix(tui): handle WSL clipboard image paths (#3990)
Fixes #3939 
Fixes #2803

## Summary
- convert Windows clipboard file paths into their `/mnt/<drive>`
equivalents when running inside WSL so pasted images resolve correctly
- add WSL detection helpers and share them with unit tests to cover both
native Windows and WSL clipboard normalization cases
- improve the test suite by exercising Windows path handling plus a
dedicated WSL conversion scenario and keeping the code path guarded by
targeted cfgs

## Testing
- just fmt
- cargo test -p codex-tui
- cargo clippy -p codex-tui --tests
- just fix -p codex-tui

## Screenshots
_Codex TUI screenshot:_
<img width="1880" height="848" alt="describe this copied image"
src="https://github.com/user-attachments/assets/c620d43c-f45c-451e-8893-e56ae85a5eea"
/>

_GitHub docs directory screenshot:_
<img width="1064" height="478" alt="image-copied"
src="https://github.com/user-attachments/assets/eb5eef6c-eb43-45a0-8bfe-25c35bcae753"
/>

Co-authored-by: Eric Traut <etraut@openai.com>
2025-12-01 16:54:20 -08:00
Steve Mostovoy
f443555728 fix(core): enable history lookup on windows (#7457)
- Add portable history log id helper to support inode-like tracking on
Unix and creation time on Windows
- Refactor history metadata and lookup to share code paths and allow
nonzero log ids across platforms
- Add coverage for lookup stability after appends
2025-12-01 16:29:01 -08:00
Celia Chen
ff4ca9959c [app-server] Add ImageView item (#7468)
Add view_image tool call as image_view item.

Before:
```
< {
<   "method": "codex/event/view_image_tool_call",
<   "params": {
<     "conversationId": "019adc2f-2922-7e43-ace9-64f394019616",
<     "id": "0",
<     "msg": {
<       "call_id": "call_nBQDxnTfZQtgjGpVoGuDnRjz",
<       "path": "/Users/celia/code/codex/codex-rs/app-server-protocol/codex-cli-login.png",
<       "type": "view_image_tool_call"
<     }
<   }
< }
```

After:
```
< {
<   "method": "item/started",
<   "params": {
<     "item": {
<       "id": "call_nBQDxnTfZQtgjGpVoGuDnRjz",
<       "path": "/Users/celia/code/codex/codex-rs/app-server-protocol/codex-cli-login.png",
<       "type": "imageView"
<     },
<     "threadId": "019adc2f-2922-7e43-ace9-64f394019616",
<     "turnId": "0"
<   }
< }

< {
<   "method": "item/completed",
<   "params": {
<     "item": {
<       "id": "call_nBQDxnTfZQtgjGpVoGuDnRjz",
<       "path": "/Users/celia/code/codex/codex-rs/app-server-protocol/codex-cli-login.png",
<       "type": "imageView"
<     },
<     "threadId": "019adc2f-2922-7e43-ace9-64f394019616",
<     "turnId": "0"
<   }
< }
```
2025-12-01 23:56:05 +00:00
Dylan Hurd
5b25915d7e fix(apply_patch) tests for shell_command (#7307)
## Summary
Adds test coverage for invocations of apply_patch via shell_command with
heredoc, to validate behavior.

## Testing
- [x] These are tests
2025-12-01 15:09:22 -08:00
Michael Bolin
c0564edebe chore: update to rmcp@0.10.0 to pick up support for custom client notifications (#7462)
In https://github.com/openai/codex/pull/7112, I updated our `rmcp`
dependency to point to a personal fork while I tried to upstream my
proposed change. Now that
https://github.com/modelcontextprotocol/rust-sdk/pull/556 has been
upstreamed and included in the `0.10.0` release of the crate, we can go
back to using the mainline release.
2025-12-01 14:01:50 -08:00
linuxmetel
c936c68c84 fix: prevent MCP startup failure on missing 'type' field (#7417)
Fix the issue #7416 that the codex-cli produce an error "MCP startup
failure on missing 'type' field" in the startup.

- Cause: serde in `convert_to_rmcp`
(`codex-rs/rmcp-client/src/utils.rs`) failed because no `r#type` value
was provided
- Fix: set a default `r#type` value in the corresponding structs
2025-12-01 13:58:20 -05:00
Kaden Gruizenga
41760f8a09 docs: clarify codex max defaults and xhigh availability (#7449)
## Summary
Adds the missing `xhigh` reasoning level everywhere it should have been
documented, and makes clear it only works with `gpt-5.1-codex-max`.

## Changes

* `docs/config.md`

* Add `xhigh` to the official list of reasoning levels with a note that
`xhigh` is exclusive to Codex Max.

* `docs/example-config.md`

* Update the example comment adding `xhigh` as a valid option but only
for Codex Max.

* `docs/faq.md`

  * Update the model recommendation to `GPT-5.1 Codex Max`.
* Mention that users can choose `high` or the newly documented `xhigh`
level when using Codex Max.
2025-12-01 10:46:53 -08:00
Albert O'Shea
440c7acd8f fix: nix build missing rmcp output hash (#7436)
Output hash for `rmcp-0.9.0` was missing from the nix package, (i.e.
`error: No hash was found while vendoring the git dependency
rmcp-0.9.0.`) blocking the build.
2025-12-01 10:45:31 -08:00
Ali Towaiji
0cc3b50228 Fix recent_commits(limit=0) returning 1 commit instead of 0 (#7334)
Fixes #7333

This is a small bug fix.

This PR fixes an inconsistency in `recent_commits` where `limit == 0`
still returns 1 commit due to the use of `limit.max(1)` when
constructing the `git log -n` argument.

Expected behavior: requesting 0 commits should return an empty list.

This PR:
- returns an empty `Vec` when `limit == 0`
- adds a test for `recent_commits(limit == 0)` that fails before the
change and passes afterwards
- maintains existing behavior for `limit > 0`

This aligns behavior with API expectations and avoids downstream
consumers misinterpreting the repository as having commit history when
`limit == 0` is used to explicitly request none.

Happy to adjust if the current behavior is intentional.
2025-12-01 10:14:36 -08:00
Owen Lin
8532876ad8 [app-server] fix: emit item/fileChange/outputDelta for file change items (#7399) 2025-12-01 17:52:34 +00:00
Owen Lin
44d92675eb [app-server] fix: ensure thread_id and turn_id are on all events (#7408)
This is an improvement for client-side developer ergonomics by
simplifying the state the client needs to keep track of.
2025-12-01 08:50:47 -08:00
jif-oai
a421eba31f fix: disable review rollout filtering (#7371) 2025-12-01 09:04:13 +00:00
Celia Chen
40006808a3 [app-server] add turn/plan/updated event (#7329)
transform `EventMsg::PlanDate` to v2 `turn/plan/updated` event. similar
to `turn/diff/updated`.
2025-11-30 21:09:59 -08:00
dependabot[bot]
ba58184349 chore(deps): bump image from 0.25.8 to 0.25.9 in /codex-rs (#7421)
Bumps [image](https://github.com/image-rs/image) from 0.25.8 to 0.25.9.
<details>
<summary>Changelog</summary>
<p><em>Sourced from <a
href="https://github.com/image-rs/image/blob/main/CHANGES.md">image's
changelog</a>.</em></p>
<blockquote>
<h3>Version 0.25.9</h3>
<p>Features:</p>
<ul>
<li>Support extracting XMP metadata from PNG, JPEG, GIF, WebP and TIFF
files (<a
href="https://redirect.github.com/image-rs/image/issues/2567">#2567</a>,
<a
href="https://redirect.github.com/image-rs/image/issues/2634">#2634</a>,
<a
href="https://redirect.github.com/image-rs/image/issues/2644">#2644</a>)</li>
<li>Support reading IPTC metadata from PNG and JPG files (<a
href="https://redirect.github.com/image-rs/image/issues/2611">#2611</a>)</li>
<li>Support reading ICC profile from GIF files (<a
href="https://redirect.github.com/image-rs/image/issues/2644">#2644</a>)</li>
<li>Allow setting a specific DEFLATE compression level when writing PNG
(<a
href="https://redirect.github.com/image-rs/image/issues/2583">#2583</a>)</li>
<li>Initial support for 16-bit CMYK TIFF files (<a
href="https://redirect.github.com/image-rs/image/issues/2588">#2588</a>)</li>
<li>Allow extracting the alpha channel of a <code>Pixel</code> in a
generic way (<a
href="https://redirect.github.com/image-rs/image/issues/2638">#2638</a>)</li>
</ul>
<p>Structural changes:</p>
<ul>
<li>EXR format decoding now only uses multi-threading via Rayon when the
<code>rayon</code> feature is enabled (<a
href="https://redirect.github.com/image-rs/image/issues/2643">#2643</a>)</li>
<li>Upgraded zune-jpeg to 0.5.x, ravif to 0.12.x, gif to 0.14.x</li>
<li>pnm: parse integers in PBM/PGM/PPM headers without allocations (<a
href="https://redirect.github.com/image-rs/image/issues/2620">#2620</a>)</li>
<li>Replace <code>doc_auto_cfg</code> with <code>doc_cfg</code> (<a
href="https://redirect.github.com/image-rs/image/issues/2637">#2637</a>)</li>
</ul>
<p>Bug fixes:</p>
<ul>
<li>Do not encode empty JPEG images (<a
href="https://redirect.github.com/image-rs/image/issues/2624">#2624</a>)</li>
<li>tga: reject empty images (<a
href="https://redirect.github.com/image-rs/image/issues/2614">#2614</a>)</li>
<li>tga: fix orientation flip for color mapped images (<a
href="https://redirect.github.com/image-rs/image/issues/2607">#2607</a>)</li>
<li>tga: adjust colormap lookup to match tga 2.0 spec (<a
href="https://redirect.github.com/image-rs/image/issues/2608">#2608</a>)</li>
</ul>
</blockquote>
</details>
<details>
<summary>Commits</summary>
<ul>
<li><a
href="5ceb6af6c2"><code>5ceb6af</code></a>
Merge pull request <a
href="https://redirect.github.com/image-rs/image/issues/2640">#2640</a>
from Shnatsel/release-v0.25.9</li>
<li><a
href="282d7b345c"><code>282d7b3</code></a>
Merge pull request <a
href="https://redirect.github.com/image-rs/image/issues/2646">#2646</a>
from oligamiq/main</li>
<li><a
href="5412aeee5a"><code>5412aee</code></a>
Amend the note in accordance with the advice of 197g.</li>
<li><a
href="4e8a4ed2e8"><code>4e8a4ed</code></a>
Clarify default features in README and add usage note</li>
<li><a
href="ca8fa528ff"><code>ca8fa52</code></a>
Merge pull request <a
href="https://redirect.github.com/image-rs/image/issues/2644">#2644</a>
from image-rs/gif-0.14</li>
<li><a
href="d9bc8fe790"><code>d9bc8fe</code></a>
mention GIF 0.14 changes</li>
<li><a
href="053220a0b1"><code>053220a</code></a>
Provide gif's XMP and ICC metadata</li>
<li><a
href="2ec20b3b3b"><code>2ec20b3</code></a>
Prepare codec with gif@0.14</li>
<li><a
href="31939facce"><code>31939fa</code></a>
Mention EXR rayon change</li>
<li><a
href="c7f68be265"><code>c7f68be</code></a>
Merge pull request <a
href="https://redirect.github.com/image-rs/image/issues/2643">#2643</a>
from Shnatsel/really-optional-rayon</li>
<li>Additional commits viewable in <a
href="https://github.com/image-rs/image/compare/v0.25.8...v0.25.9">compare
view</a></li>
</ul>
</details>
<br />


[![Dependabot compatibility
score](https://dependabot-badges.githubapp.com/badges/compatibility_score?dependency-name=image&package-manager=cargo&previous-version=0.25.8&new-version=0.25.9)](https://docs.github.com/en/github/managing-security-vulnerabilities/about-dependabot-security-updates#about-compatibility-scores)

Dependabot will resolve any conflicts with this PR as long as you don't
alter it yourself. You can also trigger a rebase manually by commenting
`@dependabot rebase`.

[//]: # (dependabot-automerge-start)
[//]: # (dependabot-automerge-end)

---

<details>
<summary>Dependabot commands and options</summary>
<br />

You can trigger Dependabot actions by commenting on this PR:
- `@dependabot rebase` will rebase this PR
- `@dependabot recreate` will recreate this PR, overwriting any edits
that have been made to it
- `@dependabot merge` will merge this PR after your CI passes on it
- `@dependabot squash and merge` will squash and merge this PR after
your CI passes on it
- `@dependabot cancel merge` will cancel a previously requested merge
and block automerging
- `@dependabot reopen` will reopen this PR if it is closed
- `@dependabot close` will close this PR and stop Dependabot recreating
it. You can achieve the same result by closing it manually
- `@dependabot show <dependency name> ignore conditions` will show all
of the ignore conditions of the specified dependency
- `@dependabot ignore this major version` will close this PR and stop
Dependabot creating any more for this major version (unless you reopen
the PR or upgrade to it yourself)
- `@dependabot ignore this minor version` will close this PR and stop
Dependabot creating any more for this minor version (unless you reopen
the PR or upgrade to it yourself)
- `@dependabot ignore this dependency` will close this PR and stop
Dependabot creating any more for this dependency (unless you reopen the
PR or upgrade to it yourself)


</details>

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-11-30 20:50:51 -08:00
Eric Traut
14df5c9492 Fixed CLA action to properly exempt dependabot (#7429) 2025-11-30 20:45:17 -08:00
dependabot[bot]
cb85a7b96e chore(deps): bump tracing from 0.1.41 to 0.1.43 in /codex-rs (#7428)
Bumps [tracing](https://github.com/tokio-rs/tracing) from 0.1.41 to
0.1.43.
<details>
<summary>Release notes</summary>
<p><em>Sourced from <a
href="https://github.com/tokio-rs/tracing/releases">tracing's
releases</a>.</em></p>
<blockquote>
<h2>tracing 0.1.43</h2>
<h4>Important</h4>
<p>The previous release [0.1.42] was yanked because <a
href="https://redirect.github.com/tokio-rs/tracing/issues/3382">#3382</a>
was a breaking change.
See further details in <a
href="https://redirect.github.com/tokio-rs/tracing/issues/3424">#3424</a>.
This release contains all the changes from that
version, plus a revert for the problematic part of the breaking PR.</p>
<h3>Fixed</h3>
<ul>
<li>Revert &quot;make <code>valueset</code> macro sanitary&quot; (<a
href="https://redirect.github.com/tokio-rs/tracing/issues/3425">#3425</a>)</li>
</ul>
<p><a
href="https://redirect.github.com/tokio-rs/tracing/issues/3382">#3382</a>:
<a
href="https://redirect.github.com/tokio-rs/tracing/pull/3382">tokio-rs/tracing#3382</a>
<a
href="https://redirect.github.com/tokio-rs/tracing/issues/3424">#3424</a>:
<a
href="https://redirect.github.com/tokio-rs/tracing/pull/3424">tokio-rs/tracing#3424</a>
<a
href="https://redirect.github.com/tokio-rs/tracing/issues/3425">#3425</a>:
<a
href="https://redirect.github.com/tokio-rs/tracing/pull/3425">tokio-rs/tracing#3425</a>
[0.1.42]: <a
href="https://github.com/tokio-rs/tracing/releases/tag/tracing-0.1.42">https://github.com/tokio-rs/tracing/releases/tag/tracing-0.1.42</a></p>
<h2>tracing 0.1.42</h2>
<h3>Important</h3>
<p>The [<code>Span::record_all</code>] method has been removed from the
documented API. It
was always unsuable via the documented API as it requried a
<code>ValueSet</code> which
has no publically documented constructors. The method remains, but
should not
be used outside of <code>tracing</code> macros.</p>
<h3>Added</h3>
<ul>
<li><strong>attributes</strong>: Support constant expressions as
instrument field names (<a
href="https://redirect.github.com/tokio-rs/tracing/issues/3158">#3158</a>)</li>
<li>Add <code>record_all!</code> macro for recording multiple values in
one call (<a
href="https://redirect.github.com/tokio-rs/tracing/issues/3227">#3227</a>)</li>
<li><strong>core</strong>: Improve code generation at trace points
significantly (<a
href="https://redirect.github.com/tokio-rs/tracing/issues/3398">#3398</a>)</li>
</ul>
<h3>Changed</h3>
<ul>
<li><code>tracing-core</code>: updated to 0.1.35 (<a
href="https://redirect.github.com/tokio-rs/tracing/issues/3414">#3414</a>)</li>
<li><code>tracing-attributes</code>: updated to 0.1.31 (<a
href="https://redirect.github.com/tokio-rs/tracing/issues/3417">#3417</a>)</li>
</ul>
<h3>Fixed</h3>
<ul>
<li>Fix &quot;name / parent&quot; variant of <code>event!</code> (<a
href="https://redirect.github.com/tokio-rs/tracing/issues/2983">#2983</a>)</li>
<li>Remove 'r#' prefix from raw identifiers in field names (<a
href="https://redirect.github.com/tokio-rs/tracing/issues/3130">#3130</a>)</li>
<li>Fix perf regression when <code>release_max_level_*</code> not set
(<a
href="https://redirect.github.com/tokio-rs/tracing/issues/3373">#3373</a>)</li>
<li>Use imported instead of fully qualified path (<a
href="https://redirect.github.com/tokio-rs/tracing/issues/3374">#3374</a>)</li>
<li>Make <code>valueset</code> macro sanitary (<a
href="https://redirect.github.com/tokio-rs/tracing/issues/3382">#3382</a>)</li>
</ul>
<h3>Documented</h3>
<ul>
<li><strong>core</strong>: Add missing <code>dyn</code> keyword in
<code>Visit</code> documentation code sample (<a
href="https://redirect.github.com/tokio-rs/tracing/issues/3387">#3387</a>)</li>
</ul>
<p><a
href="https://redirect.github.com/tokio-rs/tracing/issues/2983">#2983</a>:
<a
href="https://redirect.github.com/tokio-rs/tracing/pull/%5B#2983%5D(https://redirect.github.com/tokio-rs/tracing/issues/2983)">tokio-rs/tracing#2983</a>
<a
href="https://redirect.github.com/tokio-rs/tracing/issues/3130">#3130</a>:
<a
href="https://redirect.github.com/tokio-rs/tracing/pull/%5B#3130%5D(https://redirect.github.com/tokio-rs/tracing/issues/3130)">tokio-rs/tracing#3130</a>
<a
href="https://redirect.github.com/tokio-rs/tracing/issues/3158">#3158</a>:
<a
href="https://redirect.github.com/tokio-rs/tracing/pull/%5B#3158%5D(https://redirect.github.com/tokio-rs/tracing/issues/3158)">tokio-rs/tracing#3158</a></p>
<!-- raw HTML omitted -->
</blockquote>
<p>... (truncated)</p>
</details>
<details>
<summary>Commits</summary>
<ul>
<li><a
href="64e1c8d3ae"><code>64e1c8d</code></a>
chore: prepare tracing 0.1.43 (<a
href="https://redirect.github.com/tokio-rs/tracing/issues/3427">#3427</a>)</li>
<li><a
href="7c44f7bb21"><code>7c44f7b</code></a>
tracing: revert &quot;make <code>valueset</code> macro sanitary&quot;
(<a
href="https://redirect.github.com/tokio-rs/tracing/issues/3425">#3425</a>)</li>
<li><a
href="cdaf661c13"><code>cdaf661</code></a>
chore: prepare tracing-mock 0.1.0-beta.2 (<a
href="https://redirect.github.com/tokio-rs/tracing/issues/3422">#3422</a>)</li>
<li><a
href="a164fd3021"><code>a164fd3</code></a>
chore: prepare tracing-journald 0.3.2 (<a
href="https://redirect.github.com/tokio-rs/tracing/issues/3421">#3421</a>)</li>
<li><a
href="405397b8cc"><code>405397b</code></a>
chore: prepare tracing-appender 0.2.4 (<a
href="https://redirect.github.com/tokio-rs/tracing/issues/3420">#3420</a>)</li>
<li><a
href="a9eeed7394"><code>a9eeed7</code></a>
chore: prepare tracing-subscriber 0.3.21 (<a
href="https://redirect.github.com/tokio-rs/tracing/issues/3419">#3419</a>)</li>
<li><a
href="5bd5505478"><code>5bd5505</code></a>
chore: prepare tracing 0.1.42 (<a
href="https://redirect.github.com/tokio-rs/tracing/issues/3418">#3418</a>)</li>
<li><a
href="55086231ec"><code>5508623</code></a>
chore: prepare tracing-attributes 0.1.31 (<a
href="https://redirect.github.com/tokio-rs/tracing/issues/3417">#3417</a>)</li>
<li><a
href="d92b4c0feb"><code>d92b4c0</code></a>
chore: prepare tracing-core 0.1.35 (<a
href="https://redirect.github.com/tokio-rs/tracing/issues/3414">#3414</a>)</li>
<li><a
href="9751b6e776"><code>9751b6e</code></a>
chore: run <code>tracing-subscriber</code> tests with all features (<a
href="https://redirect.github.com/tokio-rs/tracing/issues/3412">#3412</a>)</li>
<li>Additional commits viewable in <a
href="https://github.com/tokio-rs/tracing/compare/tracing-0.1.41...tracing-0.1.43">compare
view</a></li>
</ul>
</details>
<br />


[![Dependabot compatibility
score](https://dependabot-badges.githubapp.com/badges/compatibility_score?dependency-name=tracing&package-manager=cargo&previous-version=0.1.41&new-version=0.1.43)](https://docs.github.com/en/github/managing-security-vulnerabilities/about-dependabot-security-updates#about-compatibility-scores)

Dependabot will resolve any conflicts with this PR as long as you don't
alter it yourself. You can also trigger a rebase manually by commenting
`@dependabot rebase`.

[//]: # (dependabot-automerge-start)
[//]: # (dependabot-automerge-end)

---

<details>
<summary>Dependabot commands and options</summary>
<br />

You can trigger Dependabot actions by commenting on this PR:
- `@dependabot rebase` will rebase this PR
- `@dependabot recreate` will recreate this PR, overwriting any edits
that have been made to it
- `@dependabot merge` will merge this PR after your CI passes on it
- `@dependabot squash and merge` will squash and merge this PR after
your CI passes on it
- `@dependabot cancel merge` will cancel a previously requested merge
and block automerging
- `@dependabot reopen` will reopen this PR if it is closed
- `@dependabot close` will close this PR and stop Dependabot recreating
it. You can achieve the same result by closing it manually
- `@dependabot show <dependency name> ignore conditions` will show all
of the ignore conditions of the specified dependency
- `@dependabot ignore this major version` will close this PR and stop
Dependabot creating any more for this major version (unless you reopen
the PR or upgrade to it yourself)
- `@dependabot ignore this minor version` will close this PR and stop
Dependabot creating any more for this minor version (unless you reopen
the PR or upgrade to it yourself)
- `@dependabot ignore this dependency` will close this PR and stop
Dependabot creating any more for this dependency (unless you reopen the
PR or upgrade to it yourself)


</details>

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: Eric Traut <etraut@openai.com>
2025-11-30 20:36:03 -08:00
dependabot[bot]
3f12f1140f chore(deps): bump reqwest from 0.12.23 to 0.12.24 in /codex-rs (#7424)
Bumps [reqwest](https://github.com/seanmonstar/reqwest) from 0.12.23 to
0.12.24.
<details>
<summary>Release notes</summary>
<p><em>Sourced from <a
href="https://github.com/seanmonstar/reqwest/releases">reqwest's
releases</a>.</em></p>
<blockquote>
<h2>v0.12.24</h2>
<h2>Highlights</h2>
<ul>
<li>Refactor cookie handling to an internal middleware.</li>
<li>Refactor internal random generator.</li>
<li>Refactor base64 encoding to reduce a copy.</li>
<li>Documentation updates.</li>
</ul>
<h2>What's Changed</h2>
<ul>
<li>build(deps): silence unused deps in WASM build by <a
href="https://github.com/0x676e67"><code>@​0x676e67</code></a> in <a
href="https://redirect.github.com/seanmonstar/reqwest/pull/2799">seanmonstar/reqwest#2799</a></li>
<li>perf(util): avoid extra copy when base64 encoding by <a
href="https://github.com/0x676e67"><code>@​0x676e67</code></a> in <a
href="https://redirect.github.com/seanmonstar/reqwest/pull/2805">seanmonstar/reqwest#2805</a></li>
<li>docs: fix method name in changelog entry by <a
href="https://github.com/johannespfrang"><code>@​johannespfrang</code></a>
in <a
href="https://redirect.github.com/seanmonstar/reqwest/pull/2807">seanmonstar/reqwest#2807</a></li>
<li>chore: Align the name usage of TotalTimeout by <a
href="https://github.com/Xuanwo"><code>@​Xuanwo</code></a> in <a
href="https://redirect.github.com/seanmonstar/reqwest/pull/2657">seanmonstar/reqwest#2657</a></li>
<li>refactor(cookie): add <code>CookieService</code> by <a
href="https://github.com/linyihai"><code>@​linyihai</code></a> in <a
href="https://redirect.github.com/seanmonstar/reqwest/pull/2787">seanmonstar/reqwest#2787</a></li>
<li>Fixes typo in retry max_retries_per_request doc comment re 2813 by
<a href="https://github.com/dmackinn"><code>@​dmackinn</code></a> in <a
href="https://redirect.github.com/seanmonstar/reqwest/pull/2824">seanmonstar/reqwest#2824</a></li>
<li>test(multipart): fix build failure with
<code>no-default-features</code> by <a
href="https://github.com/0x676e67"><code>@​0x676e67</code></a> in <a
href="https://redirect.github.com/seanmonstar/reqwest/pull/2801">seanmonstar/reqwest#2801</a></li>
<li>refactor(cookie): avoid duplicate cookie insertion by <a
href="https://github.com/0x676e67"><code>@​0x676e67</code></a> in <a
href="https://redirect.github.com/seanmonstar/reqwest/pull/2834">seanmonstar/reqwest#2834</a></li>
</ul>
<h2>New Contributors</h2>
<ul>
<li><a
href="https://github.com/johannespfrang"><code>@​johannespfrang</code></a>
made their first contribution in <a
href="https://redirect.github.com/seanmonstar/reqwest/pull/2807">seanmonstar/reqwest#2807</a></li>
<li><a href="https://github.com/dmackinn"><code>@​dmackinn</code></a>
made their first contribution in <a
href="https://redirect.github.com/seanmonstar/reqwest/pull/2824">seanmonstar/reqwest#2824</a></li>
</ul>
<p><strong>Full Changelog</strong>: <a
href="https://github.com/seanmonstar/reqwest/compare/v0.12.23...v0.12.24">https://github.com/seanmonstar/reqwest/compare/v0.12.23...v0.12.24</a></p>
</blockquote>
</details>
<details>
<summary>Changelog</summary>
<p><em>Sourced from <a
href="https://github.com/seanmonstar/reqwest/blob/master/CHANGELOG.md">reqwest's
changelog</a>.</em></p>
<blockquote>
<h2>v0.12.24</h2>
<ul>
<li>Refactor cookie handling to an internal middleware.</li>
<li>Refactor internal random generator.</li>
<li>Refactor base64 encoding to reduce a copy.</li>
<li>Documentation updates.</li>
</ul>
</blockquote>
</details>
<details>
<summary>Commits</summary>
<ul>
<li><a
href="b126ca49da"><code>b126ca4</code></a>
v0.12.24</li>
<li><a
href="4023493096"><code>4023493</code></a>
refactor: change fast_random from xorshift to siphash a counter</li>
<li><a
href="fd61bc93e6"><code>fd61bc9</code></a>
refactor(cookie): avoid duplicate cookie insertion (<a
href="https://redirect.github.com/seanmonstar/reqwest/issues/2834">#2834</a>)</li>
<li><a
href="0bfa526776"><code>0bfa526</code></a>
test(multipart): fix build failure with <code>no-default-features</code>
(<a
href="https://redirect.github.com/seanmonstar/reqwest/issues/2801">#2801</a>)</li>
<li><a
href="994b8a0b7a"><code>994b8a0</code></a>
docs: typo in retry max_retries_per_request (<a
href="https://redirect.github.com/seanmonstar/reqwest/issues/2824">#2824</a>)</li>
<li><a
href="da0702b762"><code>da0702b</code></a>
refactor(cookie): de-duplicate cookie support as
<code>CookieService</code> middleware (...</li>
<li><a
href="7ebddeaa87"><code>7ebddea</code></a>
chore: align internal name usage of TotalTimeout (<a
href="https://redirect.github.com/seanmonstar/reqwest/issues/2657">#2657</a>)</li>
<li><a
href="b540a4e746"><code>b540a4e</code></a>
chore(readme): use correct CI status badge</li>
<li><a
href="e4550c4cc5"><code>e4550c4</code></a>
docs: fix method name in changelog entry (<a
href="https://redirect.github.com/seanmonstar/reqwest/issues/2807">#2807</a>)</li>
<li><a
href="f4694a2922"><code>f4694a2</code></a>
perf(util): avoid extra copy when base64 encoding (<a
href="https://redirect.github.com/seanmonstar/reqwest/issues/2805">#2805</a>)</li>
<li>Additional commits viewable in <a
href="https://github.com/seanmonstar/reqwest/compare/v0.12.23...v0.12.24">compare
view</a></li>
</ul>
</details>
<br />


[![Dependabot compatibility
score](https://dependabot-badges.githubapp.com/badges/compatibility_score?dependency-name=reqwest&package-manager=cargo&previous-version=0.12.23&new-version=0.12.24)](https://docs.github.com/en/github/managing-security-vulnerabilities/about-dependabot-security-updates#about-compatibility-scores)

Dependabot will resolve any conflicts with this PR as long as you don't
alter it yourself. You can also trigger a rebase manually by commenting
`@dependabot rebase`.

[//]: # (dependabot-automerge-start)
[//]: # (dependabot-automerge-end)

---

<details>
<summary>Dependabot commands and options</summary>
<br />

You can trigger Dependabot actions by commenting on this PR:
- `@dependabot rebase` will rebase this PR
- `@dependabot recreate` will recreate this PR, overwriting any edits
that have been made to it
- `@dependabot merge` will merge this PR after your CI passes on it
- `@dependabot squash and merge` will squash and merge this PR after
your CI passes on it
- `@dependabot cancel merge` will cancel a previously requested merge
and block automerging
- `@dependabot reopen` will reopen this PR if it is closed
- `@dependabot close` will close this PR and stop Dependabot recreating
it. You can achieve the same result by closing it manually
- `@dependabot show <dependency name> ignore conditions` will show all
of the ignore conditions of the specified dependency
- `@dependabot ignore this major version` will close this PR and stop
Dependabot creating any more for this major version (unless you reopen
the PR or upgrade to it yourself)
- `@dependabot ignore this minor version` will close this PR and stop
Dependabot creating any more for this minor version (unless you reopen
the PR or upgrade to it yourself)
- `@dependabot ignore this dependency` will close this PR and stop
Dependabot creating any more for this dependency (unless you reopen the
PR or upgrade to it yourself)


</details>

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: Eric Traut <etraut@openai.com>
2025-11-30 20:35:49 -08:00
dependabot[bot]
c22cd2e953 chore(deps): bump serde_with from 3.14.0 to 3.16.1 in /codex-rs (#7422)
Bumps [serde_with](https://github.com/jonasbb/serde_with) from 3.14.0 to
3.16.1.
<details>
<summary>Release notes</summary>
<p><em>Sourced from <a
href="https://github.com/jonasbb/serde_with/releases">serde_with's
releases</a>.</em></p>
<blockquote>
<h2>serde_with v3.16.1</h2>
<h3>Fixed</h3>
<ul>
<li>Fix <code>JsonSchemaAs</code> of <code>SetPreventDuplicates</code>
and <code>SetLastValueWins</code>. (<a
href="https://redirect.github.com/jonasbb/serde_with/issues/906">#906</a>,
<a
href="https://redirect.github.com/jonasbb/serde_with/issues/907">#907</a>)</li>
</ul>
<h2>serde_with v3.16.0</h2>
<h3>Added</h3>
<ul>
<li>Added support for <code>smallvec</code> v1 under the
<code>smallvec_1</code> feature flag by <a
href="https://github.com/isharma228"><code>@​isharma228</code></a> (<a
href="https://redirect.github.com/jonasbb/serde_with/issues/895">#895</a>)</li>
<li>Add <code>JsonSchemaAs</code> implementation for
<code>json::JsonString</code> by <a
href="https://github.com/yogevm15"><code>@​yogevm15</code></a> (<a
href="https://redirect.github.com/jonasbb/serde_with/issues/901">#901</a>)</li>
</ul>
<h2>serde_with v3.15.1</h2>
<h3>Fixed</h3>
<ul>
<li>Fix building of the documentation by updating references to use
<code>serde_core</code>.</li>
</ul>
<h2>serde_with v3.15.0</h2>
<h3>Added</h3>
<ul>
<li>
<p>Added error inspection to <code>VecSkipError</code> and
<code>MapSkipError</code> by <a
href="https://github.com/michelhe"><code>@​michelhe</code></a> (<a
href="https://redirect.github.com/jonasbb/serde_with/issues/878">#878</a>)
This allows interacting with the previously hidden error, for example
for logging.
Checkout the newly added example to both types.</p>
</li>
<li>
<p>Allow documenting the types generated by <code>serde_conv!</code>.
The <code>serde_conv!</code> macro now acceps outer attributes before
the optional visibility modifier.
This allow adding doc comments in the shape of <code>#[doc =
&quot;...&quot;]</code> or any other attributes, such as lint
modifiers.</p>
<pre lang="rust"><code>serde_conv!(
    #[doc = &quot;Serialize bools as string&quot;]
    #[allow(dead_code)]
    pub BoolAsString,
    bool,
    |x: &amp;bool| ::std::string::ToString::to_string(x),
    |x: ::std::string::String| x.parse()
);
</code></pre>
</li>
<li>
<p>Add support for <code>hashbrown</code> v0.16 (<a
href="https://redirect.github.com/jonasbb/serde_with/issues/877">#877</a>)</p>
<p>This extends the existing support for <code>hashbrown</code> v0.14
and v0.15 to the newly released version.</p>
</li>
</ul>
<h3>Changed</h3>
<ul>
<li>Bump MSRV to 1.76, since that is required for <code>toml</code>
dev-dependency.</li>
</ul>
<h2>serde_with v3.14.1</h2>
<h3>Fixed</h3>
<ul>
<li>Show macro expansion in the docs.rs generated rustdoc.</li>
</ul>
<!-- raw HTML omitted -->
</blockquote>
<p>... (truncated)</p>
</details>
<details>
<summary>Commits</summary>
<ul>
<li><a
href="8513323fda"><code>8513323</code></a>
Bump version to 3.16.1 (<a
href="https://redirect.github.com/jonasbb/serde_with/issues/908">#908</a>)</li>
<li><a
href="5392bbe75e"><code>5392bbe</code></a>
Bump version to 3.16.1</li>
<li><a
href="1e54f1cd38"><code>1e54f1c</code></a>
Fix duplicate schema set definitions for schemars 0.8, 0.9, and 1.0 (<a
href="https://redirect.github.com/jonasbb/serde_with/issues/907">#907</a>)</li>
<li><a
href="0650180645"><code>0650180</code></a>
Fix duplicate schema set definitions for schemars 0.8, 0.9, and 1.0</li>
<li><a
href="41d1033438"><code>41d1033</code></a>
Fix test conditions for schemars tests to include &quot;hex&quot;
feature</li>
<li><a
href="2eed58af05"><code>2eed58a</code></a>
Bump the github-actions group across 1 directory with 2 updates (<a
href="https://redirect.github.com/jonasbb/serde_with/issues/905">#905</a>)</li>
<li><a
href="ed040f2330"><code>ed040f2</code></a>
Bump the github-actions group across 1 directory with 2 updates</li>
<li><a
href="fa2129b1b9"><code>fa2129b</code></a>
Bump ron from 0.11.0 to 0.12.0 (<a
href="https://redirect.github.com/jonasbb/serde_with/issues/904">#904</a>)</li>
<li><a
href="b55cb99757"><code>b55cb99</code></a>
Bump ron from 0.11.0 to 0.12.0</li>
<li><a
href="066b9d4019"><code>066b9d4</code></a>
Bump version to 3.16.0 (<a
href="https://redirect.github.com/jonasbb/serde_with/issues/903">#903</a>)</li>
<li>Additional commits viewable in <a
href="https://github.com/jonasbb/serde_with/compare/v3.14.0...v3.16.1">compare
view</a></li>
</ul>
</details>
<br />


[![Dependabot compatibility
score](https://dependabot-badges.githubapp.com/badges/compatibility_score?dependency-name=serde_with&package-manager=cargo&previous-version=3.14.0&new-version=3.16.1)](https://docs.github.com/en/github/managing-security-vulnerabilities/about-dependabot-security-updates#about-compatibility-scores)

Dependabot will resolve any conflicts with this PR as long as you don't
alter it yourself. You can also trigger a rebase manually by commenting
`@dependabot rebase`.

[//]: # (dependabot-automerge-start)
[//]: # (dependabot-automerge-end)

---

<details>
<summary>Dependabot commands and options</summary>
<br />

You can trigger Dependabot actions by commenting on this PR:
- `@dependabot rebase` will rebase this PR
- `@dependabot recreate` will recreate this PR, overwriting any edits
that have been made to it
- `@dependabot merge` will merge this PR after your CI passes on it
- `@dependabot squash and merge` will squash and merge this PR after
your CI passes on it
- `@dependabot cancel merge` will cancel a previously requested merge
and block automerging
- `@dependabot reopen` will reopen this PR if it is closed
- `@dependabot close` will close this PR and stop Dependabot recreating
it. You can achieve the same result by closing it manually
- `@dependabot show <dependency name> ignore conditions` will show all
of the ignore conditions of the specified dependency
- `@dependabot ignore this major version` will close this PR and stop
Dependabot creating any more for this major version (unless you reopen
the PR or upgrade to it yourself)
- `@dependabot ignore this minor version` will close this PR and stop
Dependabot creating any more for this minor version (unless you reopen
the PR or upgrade to it yourself)
- `@dependabot ignore this dependency` will close this PR and stop
Dependabot creating any more for this dependency (unless you reopen the
PR or upgrade to it yourself)


</details>

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: Eric Traut <etraut@openai.com>
2025-11-30 20:35:32 -08:00
dependabot[bot]
ebd485b1a0 chore(deps): bump arboard from 3.6.0 to 3.6.1 in /codex-rs (#7426)
Bumps [arboard](https://github.com/1Password/arboard) from 3.6.0 to
3.6.1.
<details>
<summary>Release notes</summary>
<p><em>Sourced from <a
href="https://github.com/1Password/arboard/releases">arboard's
releases</a>.</em></p>
<blockquote>
<h2>v3.6.1</h2>
<p>This release focuses on improving compatibility with data in the real
world and bug fixes. It also includes a new <code>Set</code> API for
working with file paths via drag-and-drop interfaces across Linux,
macOS, and Windows.</p>
<p>This release also marks the start of exclusively publishing
changelogs via GitHub Releases. The old <code>CHANGELOG.md</code> has
been removed due to maintenance overhead and duplication. <a
href="https://github.com/1Password/arboard/releases/tag/v3.6.0">v3.6.0</a>
is the last revision to include this file.</p>
<h3>Added</h3>
<ul>
<li>Add support for pasting lists of files via
<code>Set::file_list</code> interface by <a
href="https://github.com/Gae24"><code>@​Gae24</code></a> in <a
href="https://redirect.github.com/1Password/arboard/pull/181">1Password/arboard#181</a></li>
<li>Support <code>windows-sys</code> 0.60 in <code>arboard</code>'s
allowed version range by <a
href="https://github.com/complexspaces"><code>@​complexspaces</code></a>
in <a
href="https://redirect.github.com/1Password/arboard/pull/201">1Password/arboard#201</a></li>
</ul>
<h3>Changed</h3>
<ul>
<li>Fix grammar and typos by <a
href="https://github.com/complexspaces"><code>@​complexspaces</code></a>
and <a href="https://github.com/gagath"><code>@​gagath</code></a> in <a
href="https://redirect.github.com/1Password/arboard/pull/194">1Password/arboard#194</a>
and <a
href="https://redirect.github.com/1Password/arboard/pull/196">1Password/arboard#196</a></li>
<li>Prefer PNG when pasting images on Windows by <a
href="https://github.com/wcassels"><code>@​wcassels</code></a> in <a
href="https://redirect.github.com/1Password/arboard/pull/198">1Password/arboard#198</a>
<ul>
<li>Note: This change greatly increases compatibility for
&quot;complicated&quot; images that contain alpha values and/or
transparent pixels. Support for transparency in <code>BITMAP</code>
formats is ill-defined and inconsistently implemented in the wild, but
is consistent in <code>PNG</code>. Most applications loading images onto
the clipboard include <code>PNG</code>-encoded data already.</li>
</ul>
</li>
<li>Bitmap images pasted on Windows now use the <code>image</code> crate
instead of a homegrown internal parser.
<ul>
<li>This <strong>should not</strong> regress any existing Bitmap use
cases and instead will provide more consistent and robust parsing. If
you notice something now broken, please open an issue!</li>
</ul>
</li>
</ul>
<h3>Fixed</h3>
<ul>
<li>Remove silent dropping of file paths when non-UTF8 was mixed in on
Linux by <a href="https://github.com/Gae24"><code>@​Gae24</code></a> in
<a
href="https://redirect.github.com/1Password/arboard/pull/197">1Password/arboard#197</a></li>
<li>Fix parsing of 24-bit bitmaps on Windows by <a
href="https://github.com/wcassels"><code>@​wcassels</code></a> in <a
href="https://redirect.github.com/1Password/arboard/pull/198">1Password/arboard#198</a>
<ul>
<li>Example: Images with transparency copied by Firefox are now handled
correctly, among others.</li>
</ul>
</li>
</ul>
<h2>New Contributors</h2>
<ul>
<li><a href="https://github.com/gagath"><code>@​gagath</code></a> made
their first contribution in <a
href="https://redirect.github.com/1Password/arboard/pull/196">1Password/arboard#196</a></li>
<li><a href="https://github.com/wcassels"><code>@​wcassels</code></a>
made their first contribution in <a
href="https://redirect.github.com/1Password/arboard/pull/198">1Password/arboard#198</a></li>
</ul>
<p><strong>Full Changelog</strong>: <a
href="https://github.com/1Password/arboard/compare/v3.6.0...v3.6.1">https://github.com/1Password/arboard/compare/v3.6.0...v3.6.1</a></p>
</blockquote>
</details>
<details>
<summary>Commits</summary>
<ul>
<li><a
href="a3750c79a5"><code>a3750c7</code></a>
Release 3.6.1</li>
<li><a
href="edcce2cd6b"><code>edcce2c</code></a>
Remove CHANGELOG.md in favor of GitHub releases</li>
<li><a
href="26a96a6199"><code>26a96a6</code></a>
Bump windows-sys semver range to support 0.60.x</li>
<li><a
href="7bdd1c1175"><code>7bdd1c1</code></a>
Update errno for windows-sys 0.60 flexibility</li>
<li><a
href="55c0b260c4"><code>55c0b26</code></a>
read/write_unaligned rather than using manual field offsets</li>
<li><a
href="ff15a093d6"><code>ff15a09</code></a>
Return conversionFailure instead of adhoc errors</li>
<li><a
href="16ef18113f"><code>16ef181</code></a>
Implement fetching PNG on Windows and prefer over DIB when
available</li>
<li><a
href="a3c64f9a93"><code>a3c64f9</code></a>
Add a couple of end-to-end DIBV5 tests</li>
<li><a
href="e6008eaa91"><code>e6008ea</code></a>
Use image for reading DIB and try to make it do the right thing for
32-bit BI...</li>
<li><a
href="17ef05ce13"><code>17ef05c</code></a>
add <code>file_list</code> to <code>Set</code> interface (<a
href="https://redirect.github.com/1Password/arboard/issues/181">#181</a>)</li>
<li>Additional commits viewable in <a
href="https://github.com/1Password/arboard/compare/v3.6.0...v3.6.1">compare
view</a></li>
</ul>
</details>
<br />


[![Dependabot compatibility
score](https://dependabot-badges.githubapp.com/badges/compatibility_score?dependency-name=arboard&package-manager=cargo&previous-version=3.6.0&new-version=3.6.1)](https://docs.github.com/en/github/managing-security-vulnerabilities/about-dependabot-security-updates#about-compatibility-scores)

Dependabot will resolve any conflicts with this PR as long as you don't
alter it yourself. You can also trigger a rebase manually by commenting
`@dependabot rebase`.

[//]: # (dependabot-automerge-start)
[//]: # (dependabot-automerge-end)

---

<details>
<summary>Dependabot commands and options</summary>
<br />

You can trigger Dependabot actions by commenting on this PR:
- `@dependabot rebase` will rebase this PR
- `@dependabot recreate` will recreate this PR, overwriting any edits
that have been made to it
- `@dependabot merge` will merge this PR after your CI passes on it
- `@dependabot squash and merge` will squash and merge this PR after
your CI passes on it
- `@dependabot cancel merge` will cancel a previously requested merge
and block automerging
- `@dependabot reopen` will reopen this PR if it is closed
- `@dependabot close` will close this PR and stop Dependabot recreating
it. You can achieve the same result by closing it manually
- `@dependabot show <dependency name> ignore conditions` will show all
of the ignore conditions of the specified dependency
- `@dependabot ignore this major version` will close this PR and stop
Dependabot creating any more for this major version (unless you reopen
the PR or upgrade to it yourself)
- `@dependabot ignore this minor version` will close this PR and stop
Dependabot creating any more for this minor version (unless you reopen
the PR or upgrade to it yourself)
- `@dependabot ignore this dependency` will close this PR and stop
Dependabot creating any more for this dependency (unless you reopen the
PR or upgrade to it yourself)


</details>

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-11-30 20:20:45 -08:00
jif-oai
457c9fdb87 chore: better session recycling (#7368) 2025-11-30 12:42:26 -08:00
jif-oai
6eeaf46ac1 fix: other flaky tests (#7372) 2025-11-28 15:29:44 +00:00
jif-oai
aaec8abf58 feat: detached review (#7292) 2025-11-28 11:34:57 +00:00
Job Chong
cbd7d0d543 chore: improve rollout session init errors (#7336)
Title: Improve rollout session initialization error messages

Issue: https://github.com/openai/codex/issues/7283

What: add targeted mapping for rollout/session initialization errors so
users get actionable messages when Codex cannot access session files.

Why: session creation previously returned a generic internal error,
hiding permissions/FS issues and making support harder.

How:
- Added rollout::error::map_session_init_error to translate the more
common io::Error kinds into user-facing hints (permission, missing dir,
file blocking, corruption). Others are passed through directly with
`CodexErr::Fatal`.
- Reused the mapper in Codex session creation to preserve root causes
instead of returning InternalAgentDied.
2025-11-27 00:20:33 -08:00
Eric Traut
fabdbfef9c Fixes two bugs in example-config.md documentation (#7324)
This PR is a modified version of [a
PR](https://github.com/openai/codex/pull/7316) submitted by @yydrowz3.
* Removes a redundant `experimental_sandbox_command_assessment` flag
* Moves `mcp_oauth_credentials_store` from the `[features]` table, where
it doesn't belong
2025-11-26 09:52:13 -08:00
lionel-oai
8b314e2d04 doc: fix relative links and add tips (#7319)
This PR is a documentation only one which:
- addresses the #7231 by adding a paragraph in `docs/getting-started.md`
in the tips category to encourage users to load everything needed in
their environment
- corrects link referencing in `docs/platform-sandboxing.md` so that the
page link opens at the right section
- removes the explicit heading IDs like {#my-id} in `docs/advanced.md`
which are not supported by GitHub and are **not** rendered in the UI:

<img width="1198" height="849" alt="Screenshot 2025-11-26 at 16 25 31"
src="https://github.com/user-attachments/assets/308d33c3-81d3-4785-a6c1-e9377e6d3ea6"
/>

This caused the following links in `README.md` to not work in `main` but
to work in this branch (you can test by going to
https://github.com/openai/codex/blob/docs/getting-started-enhancement/README.md)
- the MCP link goes straight to the correct section now:

```markdown
  - [**Advanced**](./docs/advanced.md)
  - [Tracing / verbose logging](./docs/advanced.md#tracing--verbose-logging)
  - [Model Context Protocol (MCP)](./docs/advanced.md#model-context-protocol-mcp)
```

---------

Signed-off-by: lionel-oai <lionel@openai.com>
Signed-off-by: lionelchg <lionel.cheng@hotmail.fr>
Co-authored-by: lionelchg <lionel.cheng@hotmail.fr>
2025-11-26 09:35:08 -08:00
jif-oai
963009737f nit: drop file (#7314) 2025-11-26 11:30:34 +00:00
Eric Traut
e953092949 Fixed regression in experimental "sandbox command assessment" feature (#7308)
Recent model updates caused the experimental "sandbox tool assessment"
to time out most of the time leaving the user without any risk
assessment or tool summary. This change explicitly sets the reasoning
effort to medium and bumps the timeout.

This change has no effect if the user hasn't enabled the
`experimental_sandbox_command_assessment` feature flag.
2025-11-25 16:15:13 -08:00
jif-oai
28ff364c3a feat: update process ID for event handling (#7261) 2025-11-25 14:21:05 -08:00
iceweasel-oai
981e2f742d correctly recognize WorkspaceWrite policy on /approvals (#7301)
the `/approvals` popup fails to recognize that the CLI is in
WorkspaceWrite mode if that policy has extra bits, like `writable_roots`
etc.

This change matches the policy, ignoring additional config aspects.
2025-11-25 12:41:00 -08:00
Celia Chen
401f94ca31 [app-server] add thread/tokenUsage/updated v2 event (#7268)
the TokenEvent event message becomes `thread/tokenUsage/updated` in v2.
before & after:
```
< {
<   "method": "codex/event/token_count",
<   "params": {
<     "conversationId": "019ab891-4c55-7790-9670-6c3b48c33281",
<     "id": "1",
<     "msg": {
<       "info": {
<         "last_token_usage": {
<           "cached_input_tokens": 3072,
<           "input_tokens": 5152,
<           "output_tokens": 16,
<           "reasoning_output_tokens": 0,
<           "total_tokens": 5168
<         },
<         "model_context_window": 258400,
<         "total_token_usage": {
<           "cached_input_tokens": 3072,
<           "input_tokens": 5152,
<           "output_tokens": 16,
<           "reasoning_output_tokens": 0,
<           "total_tokens": 5168
<         }
<       },
<       "rate_limits": {
<         "credits": null,
<         "primary": null,
<         "secondary": null
<       },
<       "type": "token_count"
<     }
<   }
< }
< {
<   "method": "thread/tokenUsage/updated",
<   "params": {
<     "threadId": "019ab891-4c55-7790-9670-6c3b48c33281",
<     "tokenUsage": {
<       "last": {
<         "cachedInputTokens": 3072,
<         "inputTokens": 5152,
<         "outputTokens": 16,
<         "reasoningOutputTokens": 0,
<         "totalTokens": 5168
<       },
<       "modelContextWindow": 258400,
<       "total": {
<         "cachedInputTokens": 3072,
<         "inputTokens": 5152,
<         "outputTokens": 16,
<         "reasoningOutputTokens": 0,
<         "totalTokens": 5168
<       }
<     },
<     "turnId": "1"
<   }
< }
```
2025-11-25 19:56:04 +00:00
jif-oai
865e225bde tmp: drop flaky ubuntu (#7300) 2025-11-25 18:15:19 +00:00
jif-oai
4502b1b263 chore: proper client extraction (#6996) 2025-11-25 18:06:12 +00:00
jif-oai
2845e2c006 fix: drop conversation when /new (#7297) 2025-11-25 17:20:25 +00:00
jif-oai
d8a7a63959 fix: Drop MacOS 13 (#7295) 2025-11-25 16:25:44 +00:00
Owen Lin
caf2749d5b [app-server] feat: add turn/diff/updated event (#7279)
This is the V2 version of `EventMsg::TurnDiff`.

I decided to expose this as a `turn/*` notification as opposed to an
Item to make it more explicit that the diff is accumulated throughout a
turn (every `apply_patch` call updates the running diff). Also, I don't
think it's worth persisting this diff as an Item because it can always
be recomputed from the actual `FileChange` Items.
2025-11-25 16:21:08 +00:00
jif-oai
9ba27cfa0a feat: add compaction event (#7289) 2025-11-25 16:12:14 +00:00
Owen Lin
157a16cefa [app-server] feat: add thread_id and turn_id to item and error notifications (#7124)
Add `thread_id` and `turn_id` to `item/started`, `item/completed`, and
`error` notifications. Otherwise the client will have a hard time
knowing which thread & turn (if multiple threads are running in
parallel) a new item/error is for.

Also add `thread_id` to `turn/started` and `turn/completed`.
2025-11-25 08:05:47 -08:00
jif-oai
37d83e075e feat: add custom env for unified exec process (#7286) 2025-11-25 10:35:35 +00:00
jif-oai
523b40a129 feat[app-serve]: config management (#7241) 2025-11-25 09:29:38 +00:00
Celia Chen
0dd822264a [app-server-test-client] add send-followup-v2 (#7271)
Add a new endpoint that allows us to test multi-turn behavior.

Tested with running:
```
RUST_LOG=codex_app_server=debug CODEX_BIN=target/debug/codex \
      cargo run -p codex-app-server-test-client -- \
      send-follow-up-v2 "hello" "and now a follow-up question"
```
2025-11-25 08:04:27 +00:00
Clifford Ressel
3308dc5e48 fix: Correct the stream error message (#7266)
Fixes a copy paste bug with the error handling in  `try_run_turn`

I have read the CLA Document and I hereby sign the CLA
2025-11-24 20:16:29 -08:00
jif-oai
fc2ff624ac fix: don't store early exit sessions (#7263) 2025-11-24 21:14:24 +00:00
jif-oai
b897880378 chore: dedup unified exec "waited" rendering (#7256)
From
<img width="477" height="283" alt="Screenshot 2025-11-24 at 18 02 25"
src="https://github.com/user-attachments/assets/724d1d68-c994-417e-9859-ada8eb173b4c"
/>
To
<img width="444" height="174" alt="Screenshot 2025-11-24 at 18 02 40"
src="https://github.com/user-attachments/assets/40f91247-6d55-4428-84d1-f39c912ac2e7"
/>
2025-11-24 20:45:40 +00:00
iceweasel-oai
99e5340c54 Windows Sandbox: treat <workspace_root>/.git as read-only in workspace-write mode (#7142)
this functionality is
[supported](https://github.com/openai/codex/blob/main/codex-rs/protocol/src/protocol.rs#L421-L422)
in the MacOs sandbox as well. Adding it to Windows for parity

This PR also changes `rust-ci.yaml` to work around a github `hashFiles`
issue. Others have done something
[similar](https://github.com/openai/superassistant/pull/32156) today
2025-11-24 12:41:21 -08:00
Josh McKinney
ec49b56874 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
2025-11-24 12:22:18 -08:00
Josh McKinney
4ed4c73d6b chore(ci): add cargo audit workflow and policy (#7108)
- add to ignore current unmaintained advisories (derivative, fxhash,
paste) so audits gate new issues only
- introduce GitHub Actions workflow to run on push/PR using to install
cargo-audit

Existing advisories (all "unmaintained"):
- https://rustsec.org/advisories/RUSTSEC-2024-0388
- https://rustsec.org/advisories/RUSTSEC-2025-0057
- https://rustsec.org/advisories/RUSTSEC-2024-0436
2025-11-24 12:20:55 -08:00
Priyadarshini Tamilselvan
523dabc1ee fix: custom prompt expansion with large pastes (#7154)
### **Summary of Changes**

**What?**
Fix for slash commands (e.g., /prompts:code-review) not being recognized
when large content (>3000 chars) is pasted.
[Bug Report](https://github.com/openai/codex/issues/7047)
**Why?**
With large pastes, slash commands were ignored, so custom prompts
weren't expanded and were submitted as literal text.

**How?**
Refactored the early return block in handle_key_event_without_popup
(lines 957-968).
Instead of returning early after replacing placeholders, the code now
replaces placeholders in the textarea and continues to the normal
submission flow.
This reuses the existing slash command detection and custom prompt
expansion logic (lines 981-1047), avoiding duplication.

**Changes:**
Modified codex-rs/tui/src/bottom_pane/chat_composer.rs: refactored early
return block to continue to normal flow instead of returning immediately
Added test: custom_prompt_with_large_paste_expands_correctly to verify
the fix

**Code Quality:**
No lint warnings
Code follows existing patterns and reuses existing logic
Atomic change focused on the bug fix
2025-11-24 12:18:32 -08:00
Gabriel Peal
3741f387e9 Allow enterprises to skip upgrade checks and messages (#7213)
This is a feature primarily for enterprises who centrally manage Codex
updates.
2025-11-24 15:04:49 -05:00
Eric Traut
2c885edefa Added alternate form of dependabot to CLA allow list (#7260)
This fixes the CI check for CLA, which recently started to flag the
GitHub dependabot as "not having signed the CLA".
2025-11-24 12:04:27 -08:00
dependabot[bot]
f6f4c8f5ee chore(deps): bump webbrowser from 1.0.5 to 1.0.6 in /codex-rs (#7225)
Bumps [webbrowser](https://github.com/amodm/webbrowser-rs) from 1.0.5 to
1.0.6.
<details>
<summary>Release notes</summary>
<p><em>Sourced from <a
href="https://github.com/amodm/webbrowser-rs/releases">webbrowser's
releases</a>.</em></p>
<blockquote>
<h2>v1.0.6</h2>
<h3>Fixed</h3>
<ul>
<li>Windows: fix browser opening when unicode characters exist in path.
See PR <a
href="https://redirect.github.com/amodm/webbrowser-rs/issues/108">#108</a></li>
</ul>
</blockquote>
</details>
<details>
<summary>Changelog</summary>
<p><em>Sourced from <a
href="https://github.com/amodm/webbrowser-rs/blob/main/CHANGELOG.md">webbrowser's
changelog</a>.</em></p>
<blockquote>
<h2>[1.0.6] - 2025-10-15 <!-- raw HTML omitted --><!-- raw HTML omitted
--></h2>
<h3>Fixed</h3>
<ul>
<li>Windows: fix browser opening when unicode characters exist in path.
See PR <a
href="https://redirect.github.com/amodm/webbrowser-rs/issues/108">#108</a></li>
</ul>
</blockquote>
</details>
<details>
<summary>Commits</summary>
<ul>
<li><a
href="659622914a"><code>6596229</code></a>
Release v1.0.6 [skip ci]</li>
<li><a
href="44908ca5f3"><code>44908ca</code></a>
ios: fix lint for objc2 invocation #build-ios</li>
<li><a
href="b76a217a07"><code>b76a217</code></a>
Merge branch 'Nodeigi-fix/107'</li>
<li><a
href="ee2b1cdf2e"><code>ee2b1cd</code></a>
fix opening a browser that is located in a path that contains unicode
characters</li>
<li><a
href="061e65e6b8"><code>061e65e</code></a>
ios: fix lints</li>
<li><a
href="85dd4a37fc"><code>85dd4a3</code></a>
macos: fix lints</li>
<li>See full diff in <a
href="https://github.com/amodm/webbrowser-rs/compare/v1.0.5...v1.0.6">compare
view</a></li>
</ul>
</details>
<br />


[![Dependabot compatibility
score](https://dependabot-badges.githubapp.com/badges/compatibility_score?dependency-name=webbrowser&package-manager=cargo&previous-version=1.0.5&new-version=1.0.6)](https://docs.github.com/en/github/managing-security-vulnerabilities/about-dependabot-security-updates#about-compatibility-scores)

Dependabot will resolve any conflicts with this PR as long as you don't
alter it yourself. You can also trigger a rebase manually by commenting
`@dependabot rebase`.

[//]: # (dependabot-automerge-start)
[//]: # (dependabot-automerge-end)

---

<details>
<summary>Dependabot commands and options</summary>
<br />

You can trigger Dependabot actions by commenting on this PR:
- `@dependabot rebase` will rebase this PR
- `@dependabot recreate` will recreate this PR, overwriting any edits
that have been made to it
- `@dependabot merge` will merge this PR after your CI passes on it
- `@dependabot squash and merge` will squash and merge this PR after
your CI passes on it
- `@dependabot cancel merge` will cancel a previously requested merge
and block automerging
- `@dependabot reopen` will reopen this PR if it is closed
- `@dependabot close` will close this PR and stop Dependabot recreating
it. You can achieve the same result by closing it manually
- `@dependabot show <dependency name> ignore conditions` will show all
of the ignore conditions of the specified dependency
- `@dependabot ignore this major version` will close this PR and stop
Dependabot creating any more for this major version (unless you reopen
the PR or upgrade to it yourself)
- `@dependabot ignore this minor version` will close this PR and stop
Dependabot creating any more for this minor version (unless you reopen
the PR or upgrade to it yourself)
- `@dependabot ignore this dependency` will close this PR and stop
Dependabot creating any more for this dependency (unless you reopen the
PR or upgrade to it yourself)


</details>

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: Eric Traut <etraut@openai.com>
2025-11-24 12:03:10 -08:00
dependabot[bot]
1875700220 chore(deps): bump libc from 0.2.175 to 0.2.177 in /codex-rs (#7224)
Bumps [libc](https://github.com/rust-lang/libc) from 0.2.175 to 0.2.177.
<details>
<summary>Release notes</summary>
<p><em>Sourced from <a
href="https://github.com/rust-lang/libc/releases">libc's
releases</a>.</em></p>
<blockquote>
<h2>0.2.177</h2>
<h3>Added</h3>
<ul>
<li>Apple: Add <code>TIOCGETA</code>, <code>TIOCSETA</code>,
<code>TIOCSETAW</code>, <code>TIOCSETAF</code> constants (<a
href="https://redirect.github.com/rust-lang/libc/pull/4736">#4736</a>)</li>
<li>Apple: Add <code>pthread_cond_timedwait_relative_np</code> (<a
href="https://redirect.github.com/rust-lang/libc/pull/4719">#4719</a>)</li>
<li>BSDs: Add <code>_CS_PATH</code> constant (<a
href="https://redirect.github.com/rust-lang/libc/pull/4738">#4738</a>)</li>
<li>Linux-like: Add <code>SIGEMT</code> for mips* and sparc*
architectures (<a
href="https://redirect.github.com/rust-lang/libc/pull/4730">#4730</a>)</li>
<li>OpenBSD: Add <code>elf_aux_info</code> (<a
href="https://redirect.github.com/rust-lang/libc/pull/4729">#4729</a>)</li>
<li>Redox: Add more sysconf constants (<a
href="https://redirect.github.com/rust-lang/libc/pull/4728">#4728</a>)</li>
<li>Windows: Add <code>wcsnlen</code> (<a
href="https://redirect.github.com/rust-lang/libc/pull/4721">#4721</a>)</li>
</ul>
<h3>Changed</h3>
<ul>
<li>WASIP2: Invert conditional to include p2 APIs (<a
href="https://redirect.github.com/rust-lang/libc/pull/4733">#4733</a>)</li>
</ul>
<h2>0.2.176</h2>
<h3>Support</h3>
<ul>
<li>The default FreeBSD version has been raised from 11 to 12. This
matches <code>rustc</code> since 1.78. (<a
href="https://redirect.github.com/rust-lang/libc/pull/2406">#2406</a>)</li>
<li><code>Debug</code> is now always implemented, rather than being
gated behind the <code>extra_traits</code> feature. (<a
href="https://redirect.github.com/rust-lang/libc/pull/4624">#4624</a>)</li>
</ul>
<h3>Added</h3>
<ul>
<li>AIX: Restore some non-POSIX functions guarded by the
<code>_KERNEL</code> macro. (<a
href="https://redirect.github.com/rust-lang/libc/pull/4607">#4607</a>)</li>
<li>FreeBSD 14: Add <code>st_fileref</code> to <code>struct stat</code>
(<a
href="https://redirect.github.com/rust-lang/libc/pull/4642">#4642</a>)</li>
<li>Haiku: Add the <code>accept4</code> POSIX call (<a
href="https://redirect.github.com/rust-lang/libc/pull/4586">#4586</a>)</li>
<li>Introduce a wrapper for representing padding (<a
href="https://redirect.github.com/rust-lang/libc/pull/4632">#4632</a>)</li>
<li>Linux: Add <code>EM_RISCV</code> (<a
href="https://redirect.github.com/rust-lang/libc/pull/4659">#4659</a>)</li>
<li>Linux: Add <code>MS_NOSYMFOLLOW</code> (<a
href="https://redirect.github.com/rust-lang/libc/pull/4389">#4389</a>)</li>
<li>Linux: Add <code>backtrace_symbols(_fd)</code> (<a
href="https://redirect.github.com/rust-lang/libc/pull/4668">#4668</a>)</li>
<li>Linux: Add missing <code>SOL_PACKET</code> optnames (<a
href="https://redirect.github.com/rust-lang/libc/pull/4669">#4669</a>)</li>
<li>Musl s390x: Add <code>SYS_mseal</code> (<a
href="https://redirect.github.com/rust-lang/libc/pull/4549">#4549</a>)</li>
<li>NuttX: Add <code>__errno</code> (<a
href="https://redirect.github.com/rust-lang/libc/pull/4687">#4687</a>)</li>
<li>Redox: Add <code>dirfd</code>, <code>VDISABLE</code>, and resource
consts (<a
href="https://redirect.github.com/rust-lang/libc/pull/4660">#4660</a>)</li>
<li>Redox: Add more <code>resource.h</code>, <code>fcntl.h</code>
constants (<a
href="https://redirect.github.com/rust-lang/libc/pull/4666">#4666</a>)</li>
<li>Redox: Enable <code>strftime</code> and <code>mkostemp[s]</code> (<a
href="https://redirect.github.com/rust-lang/libc/pull/4629">#4629</a>)</li>
<li>Unix, Windows: Add <code>qsort_r</code> (Unix), and
<code>qsort(_s)</code> (Windows) (<a
href="https://redirect.github.com/rust-lang/libc/pull/4677">#4677</a>)</li>
<li>Unix: Add <code>dlvsym</code> for Linux-gnu, FreeBSD, and NetBSD (<a
href="https://redirect.github.com/rust-lang/libc/pull/4671">#4671</a>)</li>
<li>Unix: Add <code>sigqueue</code> (<a
href="https://redirect.github.com/rust-lang/libc/pull/4620">#4620</a>)</li>
</ul>
<h3>Changed</h3>
<ul>
<li>FreeBSD 15: Mark <code>kinfo_proc</code> as non-exhaustive (<a
href="https://redirect.github.com/rust-lang/libc/pull/4553">#4553</a>)</li>
<li>FreeBSD: Set the ELF symbol version for <code>readdir_r</code> (<a
href="https://redirect.github.com/rust-lang/libc/pull/4694">#4694</a>)</li>
<li>Linux: Correct the config for whether or not
<code>epoll_event</code> is packed (<a
href="https://redirect.github.com/rust-lang/libc/pull/4639">#4639</a>)</li>
<li>Tests: Replace the old <code>ctest</code> with the much more
reliable new implementation (<a
href="https://redirect.github.com/rust-lang/libc/pull/4655">#4655</a>
and many related PRs)</li>
</ul>
<h3>Fixed</h3>
<ul>
<li>AIX: Fix the type of the 4th arguement of <code>getgrnam_r</code>
([#4656](<a
href="https://redirect.github.com/rust-lang/libc/pull/4656">rust-lang/libc#4656</a></li>
</ul>
<!-- raw HTML omitted -->
</blockquote>
<p>... (truncated)</p>
</details>
<details>
<summary>Changelog</summary>
<p><em>Sourced from <a
href="https://github.com/rust-lang/libc/blob/0.2.177/CHANGELOG.md">libc's
changelog</a>.</em></p>
<blockquote>
<h2><a
href="https://github.com/rust-lang/libc/compare/0.2.176...0.2.177">0.2.177</a>
- 2025-10-09</h2>
<h3>Added</h3>
<ul>
<li>Apple: Add <code>TIOCGETA</code>, <code>TIOCSETA</code>,
<code>TIOCSETAW</code>, <code>TIOCSETAF</code> constants (<a
href="https://redirect.github.com/rust-lang/libc/pull/4736">#4736</a>)</li>
<li>Apple: Add <code>pthread_cond_timedwait_relative_np</code> (<a
href="https://redirect.github.com/rust-lang/libc/pull/4719">#4719</a>)</li>
<li>BSDs: Add <code>_CS_PATH</code> constant (<a
href="https://redirect.github.com/rust-lang/libc/pull/4738">#4738</a>)</li>
<li>Linux-like: Add <code>SIGEMT</code> for mips* and sparc*
architectures (<a
href="https://redirect.github.com/rust-lang/libc/pull/4730">#4730</a>)</li>
<li>OpenBSD: Add <code>elf_aux_info</code> (<a
href="https://redirect.github.com/rust-lang/libc/pull/4729">#4729</a>)</li>
<li>Redox: Add more sysconf constants (<a
href="https://redirect.github.com/rust-lang/libc/pull/4728">#4728</a>)</li>
<li>Windows: Add <code>wcsnlen</code> (<a
href="https://redirect.github.com/rust-lang/libc/pull/4721">#4721</a>)</li>
</ul>
<h3>Changed</h3>
<ul>
<li>WASIP2: Invert conditional to include p2 APIs (<a
href="https://redirect.github.com/rust-lang/libc/pull/4733">#4733</a>)</li>
</ul>
<h2><a
href="https://github.com/rust-lang/libc/compare/0.2.175...0.2.176">0.2.176</a>
- 2025-09-23</h2>
<h3>Support</h3>
<ul>
<li>The default FreeBSD version has been raised from 11 to 12. This
matches <code>rustc</code> since 1.78. (<a
href="https://redirect.github.com/rust-lang/libc/pull/2406">#2406</a>)</li>
<li><code>Debug</code> is now always implemented, rather than being
gated behind the <code>extra_traits</code> feature. (<a
href="https://redirect.github.com/rust-lang/libc/pull/4624">#4624</a>)</li>
</ul>
<h3>Added</h3>
<ul>
<li>AIX: Restore some non-POSIX functions guarded by the
<code>_KERNEL</code> macro. (<a
href="https://redirect.github.com/rust-lang/libc/pull/4607">#4607</a>)</li>
<li>FreeBSD 14: Add <code>st_fileref</code> to <code>struct stat</code>
(<a
href="https://redirect.github.com/rust-lang/libc/pull/4642">#4642</a>)</li>
<li>Haiku: Add the <code>accept4</code> POSIX call (<a
href="https://redirect.github.com/rust-lang/libc/pull/4586">#4586</a>)</li>
<li>Introduce a wrapper for representing padding (<a
href="https://redirect.github.com/rust-lang/libc/pull/4632">#4632</a>)</li>
<li>Linux: Add <code>EM_RISCV</code> (<a
href="https://redirect.github.com/rust-lang/libc/pull/4659">#4659</a>)</li>
<li>Linux: Add <code>MS_NOSYMFOLLOW</code> (<a
href="https://redirect.github.com/rust-lang/libc/pull/4389">#4389</a>)</li>
<li>Linux: Add <code>backtrace_symbols(_fd)</code> (<a
href="https://redirect.github.com/rust-lang/libc/pull/4668">#4668</a>)</li>
<li>Linux: Add missing <code>SOL_PACKET</code> optnames (<a
href="https://redirect.github.com/rust-lang/libc/pull/4669">#4669</a>)</li>
<li>Musl s390x: Add <code>SYS_mseal</code> (<a
href="https://redirect.github.com/rust-lang/libc/pull/4549">#4549</a>)</li>
<li>NuttX: Add <code>__errno</code> (<a
href="https://redirect.github.com/rust-lang/libc/pull/4687">#4687</a>)</li>
<li>Redox: Add <code>dirfd</code>, <code>VDISABLE</code>, and resource
consts (<a
href="https://redirect.github.com/rust-lang/libc/pull/4660">#4660</a>)</li>
<li>Redox: Add more <code>resource.h</code>, <code>fcntl.h</code>
constants (<a
href="https://redirect.github.com/rust-lang/libc/pull/4666">#4666</a>)</li>
<li>Redox: Enable <code>strftime</code> and <code>mkostemp[s]</code> (<a
href="https://redirect.github.com/rust-lang/libc/pull/4629">#4629</a>)</li>
<li>Unix, Windows: Add <code>qsort_r</code> (Unix), and
<code>qsort(_s)</code> (Windows) (<a
href="https://redirect.github.com/rust-lang/libc/pull/4677">#4677</a>)</li>
<li>Unix: Add <code>dlvsym</code> for Linux-gnu, FreeBSD, and NetBSD (<a
href="https://redirect.github.com/rust-lang/libc/pull/4671">#4671</a>)</li>
<li>Unix: Add <code>sigqueue</code> (<a
href="https://redirect.github.com/rust-lang/libc/pull/4620">#4620</a>)</li>
</ul>
<h3>Changed</h3>
<ul>
<li>FreeBSD 15: Mark <code>kinfo_proc</code> as non-exhaustive (<a
href="https://redirect.github.com/rust-lang/libc/pull/4553">#4553</a>)</li>
<li>FreeBSD: Set the ELF symbol version for <code>readdir_r</code> (<a
href="https://redirect.github.com/rust-lang/libc/pull/4694">#4694</a>)</li>
<li>Linux: Correct the config for whether or not
<code>epoll_event</code> is packed (<a
href="https://redirect.github.com/rust-lang/libc/pull/4639">#4639</a>)</li>
<li>Tests: Replace the old <code>ctest</code> with the much more
reliable new implementation (<a
href="https://redirect.github.com/rust-lang/libc/pull/4655">#4655</a>
and many related PRs)</li>
</ul>
<h3>Fixed</h3>
<!-- raw HTML omitted -->
</blockquote>
<p>... (truncated)</p>
</details>
<details>
<summary>Commits</summary>
<ul>
<li><a
href="9f598d245e"><code>9f598d2</code></a>
chore: release libc 0.2.177</li>
<li><a
href="329a5e77fd"><code>329a5e7</code></a>
Add missing TIOCGETA/TIOCSETA constants for macOS</li>
<li><a
href="72a40e2550"><code>72a40e2</code></a>
add <code>pthread_cond_timedwait_relative_np</code></li>
<li><a
href="2914d6f735"><code>2914d6f</code></a>
linux_like: add SIGEMT for mips* and sparc*</li>
<li><a
href="ff2ff25f15"><code>ff2ff25</code></a>
openbsd add elf_aux_info</li>
<li><a
href="4ae44a4494"><code>4ae44a4</code></a>
Update semver tests</li>
<li><a
href="d5737a0137"><code>d5737a0</code></a>
Define _CS_PATH on the BSDs</li>
<li><a
href="fe277da53e"><code>fe277da</code></a>
redox: more sysconf constants</li>
<li><a
href="bdad4264ce"><code>bdad426</code></a>
wasip2: Invert conditional to include p2 APIs</li>
<li><a
href="0af069dcbf"><code>0af069d</code></a>
Windows: add <code>wcsnlen</code></li>
<li>Additional commits viewable in <a
href="https://github.com/rust-lang/libc/compare/0.2.175...0.2.177">compare
view</a></li>
</ul>
</details>
<br />


[![Dependabot compatibility
score](https://dependabot-badges.githubapp.com/badges/compatibility_score?dependency-name=libc&package-manager=cargo&previous-version=0.2.175&new-version=0.2.177)](https://docs.github.com/en/github/managing-security-vulnerabilities/about-dependabot-security-updates#about-compatibility-scores)

Dependabot will resolve any conflicts with this PR as long as you don't
alter it yourself. You can also trigger a rebase manually by commenting
`@dependabot rebase`.

[//]: # (dependabot-automerge-start)
[//]: # (dependabot-automerge-end)

---

<details>
<summary>Dependabot commands and options</summary>
<br />

You can trigger Dependabot actions by commenting on this PR:
- `@dependabot rebase` will rebase this PR
- `@dependabot recreate` will recreate this PR, overwriting any edits
that have been made to it
- `@dependabot merge` will merge this PR after your CI passes on it
- `@dependabot squash and merge` will squash and merge this PR after
your CI passes on it
- `@dependabot cancel merge` will cancel a previously requested merge
and block automerging
- `@dependabot reopen` will reopen this PR if it is closed
- `@dependabot close` will close this PR and stop Dependabot recreating
it. You can achieve the same result by closing it manually
- `@dependabot show <dependency name> ignore conditions` will show all
of the ignore conditions of the specified dependency
- `@dependabot ignore this major version` will close this PR and stop
Dependabot creating any more for this major version (unless you reopen
the PR or upgrade to it yourself)
- `@dependabot ignore this minor version` will close this PR and stop
Dependabot creating any more for this minor version (unless you reopen
the PR or upgrade to it yourself)
- `@dependabot ignore this dependency` will close this PR and stop
Dependabot creating any more for this dependency (unless you reopen the
PR or upgrade to it yourself)


</details>

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: Eric Traut <etraut@openai.com>
2025-11-24 12:02:48 -08:00
Eric Traut
207d94b0e7 Removed streamable_shell from docs (#7235)
This config option no longer exists

Addresses #7207
2025-11-24 11:47:57 -08:00
Thomas Klausner
481c84e118 fix: Fix build process-hardening build on NetBSD (#7238)
This fixes the build of codex on NetBSD.
2025-11-24 11:46:43 -08:00
dependabot[bot]
6dd3606773 chore(deps): bump actions/checkout from 5 to 6 (#7230)
Bumps [actions/checkout](https://github.com/actions/checkout) from 5 to
6.
<details>
<summary>Release notes</summary>
<p><em>Sourced from <a
href="https://github.com/actions/checkout/releases">actions/checkout's
releases</a>.</em></p>
<blockquote>
<h2>v6.0.0</h2>
<h2>What's Changed</h2>
<ul>
<li>Update README to include Node.js 24 support details and requirements
by <a href="https://github.com/salmanmkc"><code>@​salmanmkc</code></a>
in <a
href="https://redirect.github.com/actions/checkout/pull/2248">actions/checkout#2248</a></li>
<li>Persist creds to a separate file by <a
href="https://github.com/ericsciple"><code>@​ericsciple</code></a> in <a
href="https://redirect.github.com/actions/checkout/pull/2286">actions/checkout#2286</a></li>
<li>v6-beta by <a
href="https://github.com/ericsciple"><code>@​ericsciple</code></a> in <a
href="https://redirect.github.com/actions/checkout/pull/2298">actions/checkout#2298</a></li>
<li>update readme/changelog for v6 by <a
href="https://github.com/ericsciple"><code>@​ericsciple</code></a> in <a
href="https://redirect.github.com/actions/checkout/pull/2311">actions/checkout#2311</a></li>
</ul>
<p><strong>Full Changelog</strong>: <a
href="https://github.com/actions/checkout/compare/v5.0.0...v6.0.0">https://github.com/actions/checkout/compare/v5.0.0...v6.0.0</a></p>
<h2>v6-beta</h2>
<h2>What's Changed</h2>
<p>Updated persist-credentials to store the credentials under
<code>$RUNNER_TEMP</code> instead of directly in the local git
config.</p>
<p>This requires a minimum Actions Runner version of <a
href="https://github.com/actions/runner/releases/tag/v2.329.0">v2.329.0</a>
to access the persisted credentials for <a
href="https://docs.github.com/en/actions/tutorials/use-containerized-services/create-a-docker-container-action">Docker
container action</a> scenarios.</p>
<h2>v5.0.1</h2>
<h2>What's Changed</h2>
<ul>
<li>Port v6 cleanup to v5 by <a
href="https://github.com/ericsciple"><code>@​ericsciple</code></a> in <a
href="https://redirect.github.com/actions/checkout/pull/2301">actions/checkout#2301</a></li>
</ul>
<p><strong>Full Changelog</strong>: <a
href="https://github.com/actions/checkout/compare/v5...v5.0.1">https://github.com/actions/checkout/compare/v5...v5.0.1</a></p>
</blockquote>
</details>
<details>
<summary>Changelog</summary>
<p><em>Sourced from <a
href="https://github.com/actions/checkout/blob/main/CHANGELOG.md">actions/checkout's
changelog</a>.</em></p>
<blockquote>
<h1>Changelog</h1>
<h2>V6.0.0</h2>
<ul>
<li>Persist creds to a separate file by <a
href="https://github.com/ericsciple"><code>@​ericsciple</code></a> in <a
href="https://redirect.github.com/actions/checkout/pull/2286">actions/checkout#2286</a></li>
<li>Update README to include Node.js 24 support details and requirements
by <a href="https://github.com/salmanmkc"><code>@​salmanmkc</code></a>
in <a
href="https://redirect.github.com/actions/checkout/pull/2248">actions/checkout#2248</a></li>
</ul>
<h2>V5.0.1</h2>
<ul>
<li>Port v6 cleanup to v5 by <a
href="https://github.com/ericsciple"><code>@​ericsciple</code></a> in <a
href="https://redirect.github.com/actions/checkout/pull/2301">actions/checkout#2301</a></li>
</ul>
<h2>V5.0.0</h2>
<ul>
<li>Update actions checkout to use node 24 by <a
href="https://github.com/salmanmkc"><code>@​salmanmkc</code></a> in <a
href="https://redirect.github.com/actions/checkout/pull/2226">actions/checkout#2226</a></li>
</ul>
<h2>V4.3.1</h2>
<ul>
<li>Port v6 cleanup to v4 by <a
href="https://github.com/ericsciple"><code>@​ericsciple</code></a> in <a
href="https://redirect.github.com/actions/checkout/pull/2305">actions/checkout#2305</a></li>
</ul>
<h2>V4.3.0</h2>
<ul>
<li>docs: update README.md by <a
href="https://github.com/motss"><code>@​motss</code></a> in <a
href="https://redirect.github.com/actions/checkout/pull/1971">actions/checkout#1971</a></li>
<li>Add internal repos for checking out multiple repositories by <a
href="https://github.com/mouismail"><code>@​mouismail</code></a> in <a
href="https://redirect.github.com/actions/checkout/pull/1977">actions/checkout#1977</a></li>
<li>Documentation update - add recommended permissions to Readme by <a
href="https://github.com/benwells"><code>@​benwells</code></a> in <a
href="https://redirect.github.com/actions/checkout/pull/2043">actions/checkout#2043</a></li>
<li>Adjust positioning of user email note and permissions heading by <a
href="https://github.com/joshmgross"><code>@​joshmgross</code></a> in <a
href="https://redirect.github.com/actions/checkout/pull/2044">actions/checkout#2044</a></li>
<li>Update README.md by <a
href="https://github.com/nebuk89"><code>@​nebuk89</code></a> in <a
href="https://redirect.github.com/actions/checkout/pull/2194">actions/checkout#2194</a></li>
<li>Update CODEOWNERS for actions by <a
href="https://github.com/TingluoHuang"><code>@​TingluoHuang</code></a>
in <a
href="https://redirect.github.com/actions/checkout/pull/2224">actions/checkout#2224</a></li>
<li>Update package dependencies by <a
href="https://github.com/salmanmkc"><code>@​salmanmkc</code></a> in <a
href="https://redirect.github.com/actions/checkout/pull/2236">actions/checkout#2236</a></li>
</ul>
<h2>v4.2.2</h2>
<ul>
<li><code>url-helper.ts</code> now leverages well-known environment
variables by <a href="https://github.com/jww3"><code>@​jww3</code></a>
in <a
href="https://redirect.github.com/actions/checkout/pull/1941">actions/checkout#1941</a></li>
<li>Expand unit test coverage for <code>isGhes</code> by <a
href="https://github.com/jww3"><code>@​jww3</code></a> in <a
href="https://redirect.github.com/actions/checkout/pull/1946">actions/checkout#1946</a></li>
</ul>
<h2>v4.2.1</h2>
<ul>
<li>Check out other refs/* by commit if provided, fall back to ref by <a
href="https://github.com/orhantoy"><code>@​orhantoy</code></a> in <a
href="https://redirect.github.com/actions/checkout/pull/1924">actions/checkout#1924</a></li>
</ul>
<h2>v4.2.0</h2>
<ul>
<li>Add Ref and Commit outputs by <a
href="https://github.com/lucacome"><code>@​lucacome</code></a> in <a
href="https://redirect.github.com/actions/checkout/pull/1180">actions/checkout#1180</a></li>
<li>Dependency updates by <a
href="https://github.com/dependabot"><code>@​dependabot</code></a>- <a
href="https://redirect.github.com/actions/checkout/pull/1777">actions/checkout#1777</a>,
<a
href="https://redirect.github.com/actions/checkout/pull/1872">actions/checkout#1872</a></li>
</ul>
<h2>v4.1.7</h2>
<ul>
<li>Bump the minor-npm-dependencies group across 1 directory with 4
updates by <a
href="https://github.com/dependabot"><code>@​dependabot</code></a> in <a
href="https://redirect.github.com/actions/checkout/pull/1739">actions/checkout#1739</a></li>
<li>Bump actions/checkout from 3 to 4 by <a
href="https://github.com/dependabot"><code>@​dependabot</code></a> in <a
href="https://redirect.github.com/actions/checkout/pull/1697">actions/checkout#1697</a></li>
<li>Check out other refs/* by commit by <a
href="https://github.com/orhantoy"><code>@​orhantoy</code></a> in <a
href="https://redirect.github.com/actions/checkout/pull/1774">actions/checkout#1774</a></li>
<li>Pin actions/checkout's own workflows to a known, good, stable
version. by <a href="https://github.com/jww3"><code>@​jww3</code></a> in
<a
href="https://redirect.github.com/actions/checkout/pull/1776">actions/checkout#1776</a></li>
</ul>
<h2>v4.1.6</h2>
<ul>
<li>Check platform to set archive extension appropriately by <a
href="https://github.com/cory-miller"><code>@​cory-miller</code></a> in
<a
href="https://redirect.github.com/actions/checkout/pull/1732">actions/checkout#1732</a></li>
</ul>
<h2>v4.1.5</h2>
<ul>
<li>Update NPM dependencies by <a
href="https://github.com/cory-miller"><code>@​cory-miller</code></a> in
<a
href="https://redirect.github.com/actions/checkout/pull/1703">actions/checkout#1703</a></li>
<li>Bump github/codeql-action from 2 to 3 by <a
href="https://github.com/dependabot"><code>@​dependabot</code></a> in <a
href="https://redirect.github.com/actions/checkout/pull/1694">actions/checkout#1694</a></li>
<li>Bump actions/setup-node from 1 to 4 by <a
href="https://github.com/dependabot"><code>@​dependabot</code></a> in <a
href="https://redirect.github.com/actions/checkout/pull/1696">actions/checkout#1696</a></li>
<li>Bump actions/upload-artifact from 2 to 4 by <a
href="https://github.com/dependabot"><code>@​dependabot</code></a> in <a
href="https://redirect.github.com/actions/checkout/pull/1695">actions/checkout#1695</a></li>
</ul>
<!-- raw HTML omitted -->
</blockquote>
<p>... (truncated)</p>
</details>
<details>
<summary>Commits</summary>
<ul>
<li><a
href="1af3b93b68"><code>1af3b93</code></a>
update readme/changelog for v6 (<a
href="https://redirect.github.com/actions/checkout/issues/2311">#2311</a>)</li>
<li><a
href="71cf2267d8"><code>71cf226</code></a>
v6-beta (<a
href="https://redirect.github.com/actions/checkout/issues/2298">#2298</a>)</li>
<li><a
href="069c695914"><code>069c695</code></a>
Persist creds to a separate file (<a
href="https://redirect.github.com/actions/checkout/issues/2286">#2286</a>)</li>
<li><a
href="ff7abcd0c3"><code>ff7abcd</code></a>
Update README to include Node.js 24 support details and requirements (<a
href="https://redirect.github.com/actions/checkout/issues/2248">#2248</a>)</li>
<li>See full diff in <a
href="https://github.com/actions/checkout/compare/v5...v6">compare
view</a></li>
</ul>
</details>
<br />


[![Dependabot compatibility
score](https://dependabot-badges.githubapp.com/badges/compatibility_score?dependency-name=actions/checkout&package-manager=github_actions&previous-version=5&new-version=6)](https://docs.github.com/en/github/managing-security-vulnerabilities/about-dependabot-security-updates#about-compatibility-scores)

Dependabot will resolve any conflicts with this PR as long as you don't
alter it yourself. You can also trigger a rebase manually by commenting
`@dependabot rebase`.

[//]: # (dependabot-automerge-start)
[//]: # (dependabot-automerge-end)

---

<details>
<summary>Dependabot commands and options</summary>
<br />

You can trigger Dependabot actions by commenting on this PR:
- `@dependabot rebase` will rebase this PR
- `@dependabot recreate` will recreate this PR, overwriting any edits
that have been made to it
- `@dependabot merge` will merge this PR after your CI passes on it
- `@dependabot squash and merge` will squash and merge this PR after
your CI passes on it
- `@dependabot cancel merge` will cancel a previously requested merge
and block automerging
- `@dependabot reopen` will reopen this PR if it is closed
- `@dependabot close` will close this PR and stop Dependabot recreating
it. You can achieve the same result by closing it manually
- `@dependabot show <dependency name> ignore conditions` will show all
of the ignore conditions of the specified dependency
- `@dependabot ignore this major version` will close this PR and stop
Dependabot creating any more for this major version (unless you reopen
the PR or upgrade to it yourself)
- `@dependabot ignore this minor version` will close this PR and stop
Dependabot creating any more for this minor version (unless you reopen
the PR or upgrade to it yourself)
- `@dependabot ignore this dependency` will close this PR and stop
Dependabot creating any more for this dependency (unless you reopen the
PR or upgrade to it yourself)


</details>

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-11-24 11:45:57 -08:00
dependabot[bot]
35850caff6 chore(deps): bump actions/upload-artifact from 4 to 5 (#7229)
Bumps
[actions/upload-artifact](https://github.com/actions/upload-artifact)
from 4 to 5.
<details>
<summary>Release notes</summary>
<p><em>Sourced from <a
href="https://github.com/actions/upload-artifact/releases">actions/upload-artifact's
releases</a>.</em></p>
<blockquote>
<h2>v5.0.0</h2>
<h2>What's Changed</h2>
<p><strong>BREAKING CHANGE:</strong> this update supports Node
<code>v24.x</code>. This is not a breaking change per-se but we're
treating it as such.</p>
<ul>
<li>Update README.md by <a
href="https://github.com/GhadimiR"><code>@​GhadimiR</code></a> in <a
href="https://redirect.github.com/actions/upload-artifact/pull/681">actions/upload-artifact#681</a></li>
<li>Update README.md by <a
href="https://github.com/nebuk89"><code>@​nebuk89</code></a> in <a
href="https://redirect.github.com/actions/upload-artifact/pull/712">actions/upload-artifact#712</a></li>
<li>Readme: spell out the first use of GHES by <a
href="https://github.com/danwkennedy"><code>@​danwkennedy</code></a> in
<a
href="https://redirect.github.com/actions/upload-artifact/pull/727">actions/upload-artifact#727</a></li>
<li>Update GHES guidance to include reference to Node 20 version by <a
href="https://github.com/patrikpolyak"><code>@​patrikpolyak</code></a>
in <a
href="https://redirect.github.com/actions/upload-artifact/pull/725">actions/upload-artifact#725</a></li>
<li>Bump <code>@actions/artifact</code> to <code>v4.0.0</code></li>
<li>Prepare <code>v5.0.0</code> by <a
href="https://github.com/danwkennedy"><code>@​danwkennedy</code></a> in
<a
href="https://redirect.github.com/actions/upload-artifact/pull/734">actions/upload-artifact#734</a></li>
</ul>
<h2>New Contributors</h2>
<ul>
<li><a href="https://github.com/GhadimiR"><code>@​GhadimiR</code></a>
made their first contribution in <a
href="https://redirect.github.com/actions/upload-artifact/pull/681">actions/upload-artifact#681</a></li>
<li><a href="https://github.com/nebuk89"><code>@​nebuk89</code></a> made
their first contribution in <a
href="https://redirect.github.com/actions/upload-artifact/pull/712">actions/upload-artifact#712</a></li>
<li><a
href="https://github.com/danwkennedy"><code>@​danwkennedy</code></a>
made their first contribution in <a
href="https://redirect.github.com/actions/upload-artifact/pull/727">actions/upload-artifact#727</a></li>
<li><a
href="https://github.com/patrikpolyak"><code>@​patrikpolyak</code></a>
made their first contribution in <a
href="https://redirect.github.com/actions/upload-artifact/pull/725">actions/upload-artifact#725</a></li>
</ul>
<p><strong>Full Changelog</strong>: <a
href="https://github.com/actions/upload-artifact/compare/v4...v5.0.0">https://github.com/actions/upload-artifact/compare/v4...v5.0.0</a></p>
<h2>v4.6.2</h2>
<h2>What's Changed</h2>
<ul>
<li>Update to use artifact 2.3.2 package &amp; prepare for new
upload-artifact release by <a
href="https://github.com/salmanmkc"><code>@​salmanmkc</code></a> in <a
href="https://redirect.github.com/actions/upload-artifact/pull/685">actions/upload-artifact#685</a></li>
</ul>
<h2>New Contributors</h2>
<ul>
<li><a href="https://github.com/salmanmkc"><code>@​salmanmkc</code></a>
made their first contribution in <a
href="https://redirect.github.com/actions/upload-artifact/pull/685">actions/upload-artifact#685</a></li>
</ul>
<p><strong>Full Changelog</strong>: <a
href="https://github.com/actions/upload-artifact/compare/v4...v4.6.2">https://github.com/actions/upload-artifact/compare/v4...v4.6.2</a></p>
<h2>v4.6.1</h2>
<h2>What's Changed</h2>
<ul>
<li>Update to use artifact 2.2.2 package by <a
href="https://github.com/yacaovsnc"><code>@​yacaovsnc</code></a> in <a
href="https://redirect.github.com/actions/upload-artifact/pull/673">actions/upload-artifact#673</a></li>
</ul>
<p><strong>Full Changelog</strong>: <a
href="https://github.com/actions/upload-artifact/compare/v4...v4.6.1">https://github.com/actions/upload-artifact/compare/v4...v4.6.1</a></p>
<h2>v4.6.0</h2>
<h2>What's Changed</h2>
<ul>
<li>Expose env vars to control concurrency and timeout by <a
href="https://github.com/yacaovsnc"><code>@​yacaovsnc</code></a> in <a
href="https://redirect.github.com/actions/upload-artifact/pull/662">actions/upload-artifact#662</a></li>
</ul>
<p><strong>Full Changelog</strong>: <a
href="https://github.com/actions/upload-artifact/compare/v4...v4.6.0">https://github.com/actions/upload-artifact/compare/v4...v4.6.0</a></p>
<h2>v4.5.0</h2>
<h2>What's Changed</h2>
<ul>
<li>fix: deprecated <code>Node.js</code> version in action by <a
href="https://github.com/hamirmahal"><code>@​hamirmahal</code></a> in <a
href="https://redirect.github.com/actions/upload-artifact/pull/578">actions/upload-artifact#578</a></li>
<li>Add new <code>artifact-digest</code> output by <a
href="https://github.com/bdehamer"><code>@​bdehamer</code></a> in <a
href="https://redirect.github.com/actions/upload-artifact/pull/656">actions/upload-artifact#656</a></li>
</ul>
<h2>New Contributors</h2>
<ul>
<li><a
href="https://github.com/hamirmahal"><code>@​hamirmahal</code></a> made
their first contribution in <a
href="https://redirect.github.com/actions/upload-artifact/pull/578">actions/upload-artifact#578</a></li>
</ul>
<!-- raw HTML omitted -->
</blockquote>
<p>... (truncated)</p>
</details>
<details>
<summary>Commits</summary>
<ul>
<li><a
href="330a01c490"><code>330a01c</code></a>
Merge pull request <a
href="https://redirect.github.com/actions/upload-artifact/issues/734">#734</a>
from actions/danwkennedy/prepare-5.0.0</li>
<li><a
href="03f2824452"><code>03f2824</code></a>
Update <code>github.dep.yml</code></li>
<li><a
href="905a1ecb59"><code>905a1ec</code></a>
Prepare <code>v5.0.0</code></li>
<li><a
href="2d9f9cdfa9"><code>2d9f9cd</code></a>
Merge pull request <a
href="https://redirect.github.com/actions/upload-artifact/issues/725">#725</a>
from patrikpolyak/patch-1</li>
<li><a
href="9687587dec"><code>9687587</code></a>
Merge branch 'main' into patch-1</li>
<li><a
href="2848b2cda0"><code>2848b2c</code></a>
Merge pull request <a
href="https://redirect.github.com/actions/upload-artifact/issues/727">#727</a>
from danwkennedy/patch-1</li>
<li><a
href="9b511775fd"><code>9b51177</code></a>
Spell out the first use of GHES</li>
<li><a
href="cd231ca1ed"><code>cd231ca</code></a>
Update GHES guidance to include reference to Node 20 version</li>
<li><a
href="de65e23aa2"><code>de65e23</code></a>
Merge pull request <a
href="https://redirect.github.com/actions/upload-artifact/issues/712">#712</a>
from actions/nebuk89-patch-1</li>
<li><a
href="8747d8cd76"><code>8747d8c</code></a>
Update README.md</li>
<li>Additional commits viewable in <a
href="https://github.com/actions/upload-artifact/compare/v4...v5">compare
view</a></li>
</ul>
</details>
<br />


[![Dependabot compatibility
score](https://dependabot-badges.githubapp.com/badges/compatibility_score?dependency-name=actions/upload-artifact&package-manager=github_actions&previous-version=4&new-version=5)](https://docs.github.com/en/github/managing-security-vulnerabilities/about-dependabot-security-updates#about-compatibility-scores)

Dependabot will resolve any conflicts with this PR as long as you don't
alter it yourself. You can also trigger a rebase manually by commenting
`@dependabot rebase`.

[//]: # (dependabot-automerge-start)
[//]: # (dependabot-automerge-end)

---

<details>
<summary>Dependabot commands and options</summary>
<br />

You can trigger Dependabot actions by commenting on this PR:
- `@dependabot rebase` will rebase this PR
- `@dependabot recreate` will recreate this PR, overwriting any edits
that have been made to it
- `@dependabot merge` will merge this PR after your CI passes on it
- `@dependabot squash and merge` will squash and merge this PR after
your CI passes on it
- `@dependabot cancel merge` will cancel a previously requested merge
and block automerging
- `@dependabot reopen` will reopen this PR if it is closed
- `@dependabot close` will close this PR and stop Dependabot recreating
it. You can achieve the same result by closing it manually
- `@dependabot show <dependency name> ignore conditions` will show all
of the ignore conditions of the specified dependency
- `@dependabot ignore this major version` will close this PR and stop
Dependabot creating any more for this major version (unless you reopen
the PR or upgrade to it yourself)
- `@dependabot ignore this minor version` will close this PR and stop
Dependabot creating any more for this minor version (unless you reopen
the PR or upgrade to it yourself)
- `@dependabot ignore this dependency` will close this PR and stop
Dependabot creating any more for this dependency (unless you reopen the
PR or upgrade to it yourself)


</details>

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-11-24 11:45:29 -08:00
dependabot[bot]
e8b6fb7937 chore(deps): bump toml_edit from 0.23.4 to 0.23.5 in /codex-rs (#7223)
Bumps [toml_edit](https://github.com/toml-rs/toml) from 0.23.4 to
0.23.5.
<details>
<summary>Commits</summary>
<ul>
<li><a
href="4695fb02fc"><code>4695fb0</code></a>
chore: Release</li>
<li><a
href="6a77ed71cf"><code>6a77ed7</code></a>
docs: Update changelog</li>
<li><a
href="c1e8197964"><code>c1e8197</code></a>
refactor: Switch serde dependency to serde_core (<a
href="https://redirect.github.com/toml-rs/toml/issues/1036">#1036</a>)</li>
<li><a
href="d85d6cd61c"><code>d85d6cd</code></a>
refactor: Switch serde dependency to serde_core</li>
<li>See full diff in <a
href="https://github.com/toml-rs/toml/compare/v0.23.4...v0.23.5">compare
view</a></li>
</ul>
</details>
<br />


[![Dependabot compatibility
score](https://dependabot-badges.githubapp.com/badges/compatibility_score?dependency-name=toml_edit&package-manager=cargo&previous-version=0.23.4&new-version=0.23.5)](https://docs.github.com/en/github/managing-security-vulnerabilities/about-dependabot-security-updates#about-compatibility-scores)

Dependabot will resolve any conflicts with this PR as long as you don't
alter it yourself. You can also trigger a rebase manually by commenting
`@dependabot rebase`.

[//]: # (dependabot-automerge-start)
[//]: # (dependabot-automerge-end)

---

<details>
<summary>Dependabot commands and options</summary>
<br />

You can trigger Dependabot actions by commenting on this PR:
- `@dependabot rebase` will rebase this PR
- `@dependabot recreate` will recreate this PR, overwriting any edits
that have been made to it
- `@dependabot merge` will merge this PR after your CI passes on it
- `@dependabot squash and merge` will squash and merge this PR after
your CI passes on it
- `@dependabot cancel merge` will cancel a previously requested merge
and block automerging
- `@dependabot reopen` will reopen this PR if it is closed
- `@dependabot close` will close this PR and stop Dependabot recreating
it. You can achieve the same result by closing it manually
- `@dependabot show <dependency name> ignore conditions` will show all
of the ignore conditions of the specified dependency
- `@dependabot ignore this major version` will close this PR and stop
Dependabot creating any more for this major version (unless you reopen
the PR or upgrade to it yourself)
- `@dependabot ignore this minor version` will close this PR and stop
Dependabot creating any more for this minor version (unless you reopen
the PR or upgrade to it yourself)
- `@dependabot ignore this dependency` will close this PR and stop
Dependabot creating any more for this dependency (unless you reopen the
PR or upgrade to it yourself)


</details>

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-11-24 11:44:14 -08:00
dependabot[bot]
be022be381 chore(deps): bump regex from 1.11.1 to 1.12.2 in /codex-rs (#7222)
Bumps [regex](https://github.com/rust-lang/regex) from 1.11.1 to 1.12.2.
<details>
<summary>Changelog</summary>
<p><em>Sourced from <a
href="https://github.com/rust-lang/regex/blob/master/CHANGELOG.md">regex's
changelog</a>.</em></p>
<blockquote>
<h1>1.12.2 (2025-10-13)</h1>
<p>This release fixes a <code>cargo doc</code> breakage on nightly when
<code>--cfg docsrs</code> is
enabled. This caused documentation to fail to build on docs.rs.</p>
<p>Bug fixes:</p>
<ul>
<li>[BUG <a
href="https://redirect.github.com/rust-lang/regex/issues/1305">#1305</a>](<a
href="https://redirect.github.com/rust-lang/regex/issues/1305">rust-lang/regex#1305</a>):
Switches the <code>doc_auto_cfg</code> feature to <code>doc_cfg</code>
on nightly for docs.rs builds.</li>
</ul>
<h1>1.12.1 (2025-10-10)</h1>
<p>This release makes a bug fix in the new
<code>regex::Captures::get_match</code> API
introduced in <code>1.12.0</code>. There was an oversight with the
lifetime parameter
for the <code>Match</code> returned. This is technically a breaking
change, but given
that it was caught almost immediately and I've yanked the
<code>1.12.0</code> release,
I think this is fine.</p>
<h1>1.12.0 (2025-10-10)</h1>
<p>This release contains a smattering of bug fixes, a fix for excessive
memory
consumption in some cases and a new
<code>regex::Captures::get_match</code> API.</p>
<p>Improvements:</p>
<ul>
<li>[FEATURE <a
href="https://redirect.github.com/rust-lang/regex/issues/1146">#1146</a>](<a
href="https://redirect.github.com/rust-lang/regex/issues/1146">rust-lang/regex#1146</a>):
Add <code>Capture::get_match</code> for returning the overall match
without <code>unwrap()</code>.</li>
</ul>
<p>Bug fixes:</p>
<ul>
<li>[BUG <a
href="https://redirect.github.com/rust-lang/regex/issues/1083">#1083</a>](<a
href="https://redirect.github.com/rust-lang/regex/issues/1083">rust-lang/regex#1083</a>):
Fixes a panic in the lazy DFA (can only occur for especially large
regexes).</li>
<li>[BUG <a
href="https://redirect.github.com/rust-lang/regex/issues/1116">#1116</a>](<a
href="https://redirect.github.com/rust-lang/regex/issues/1116">rust-lang/regex#1116</a>):
Fixes a memory usage regression for large regexes (introduced in
<code>regex 1.9</code>).</li>
<li>[BUG <a
href="https://redirect.github.com/rust-lang/regex/issues/1195">#1195</a>](<a
href="https://redirect.github.com/rust-lang/regex/issues/1195">rust-lang/regex#1195</a>):
Fix universal start states in sparse DFA.</li>
<li>[BUG <a
href="https://redirect.github.com/rust-lang/regex/issues/1295">#1295</a>](<a
href="https://redirect.github.com/rust-lang/regex/pull/1295">rust-lang/regex#1295</a>):
Fixes a panic when deserializing a corrupted dense DFA.</li>
<li><a
href="8f5d9479d0">BUG
8f5d9479</a>:
Make <code>regex_automata::meta::Regex::find</code> consistently return
<code>None</code> when
<code>WhichCaptures::None</code> is used.</li>
</ul>
<h1>1.11.3 (2025-09-25)</h1>
<p>This is a small patch release with an improvement in memory usage in
some
cases.</p>
<!-- raw HTML omitted -->
</blockquote>
<p>... (truncated)</p>
</details>
<details>
<summary>Commits</summary>
<ul>
<li><a
href="5ea3eb1e95"><code>5ea3eb1</code></a>
1.12.2</li>
<li><a
href="ab0b07171b"><code>ab0b071</code></a>
regex-automata-0.4.13</li>
<li><a
href="691d51457d"><code>691d514</code></a>
regex-syntax-0.8.8</li>
<li><a
href="1dd9077779"><code>1dd9077</code></a>
docs: swap <code>doc_auto_cfg</code> with <code>doc_cfg</code></li>
<li><a
href="0089034cb3"><code>0089034</code></a>
regex-cli-0.2.3</li>
<li><a
href="140f8949da"><code>140f894</code></a>
regex-lite-0.1.8</li>
<li><a
href="27d6d65263"><code>27d6d65</code></a>
1.12.1</li>
<li><a
href="85398ad500"><code>85398ad</code></a>
changelog: 1.12.1</li>
<li><a
href="764efbd305"><code>764efbd</code></a>
api: tweak the lifetime of <code>Captures::get_match</code></li>
<li><a
href="ee6aa55e01"><code>ee6aa55</code></a>
rure-0.2.4</li>
<li>Additional commits viewable in <a
href="https://github.com/rust-lang/regex/compare/1.11.1...1.12.2">compare
view</a></li>
</ul>
</details>
<br />


[![Dependabot compatibility
score](https://dependabot-badges.githubapp.com/badges/compatibility_score?dependency-name=regex&package-manager=cargo&previous-version=1.11.1&new-version=1.12.2)](https://docs.github.com/en/github/managing-security-vulnerabilities/about-dependabot-security-updates#about-compatibility-scores)

Dependabot will resolve any conflicts with this PR as long as you don't
alter it yourself. You can also trigger a rebase manually by commenting
`@dependabot rebase`.

[//]: # (dependabot-automerge-start)
[//]: # (dependabot-automerge-end)

---

<details>
<summary>Dependabot commands and options</summary>
<br />

You can trigger Dependabot actions by commenting on this PR:
- `@dependabot rebase` will rebase this PR
- `@dependabot recreate` will recreate this PR, overwriting any edits
that have been made to it
- `@dependabot merge` will merge this PR after your CI passes on it
- `@dependabot squash and merge` will squash and merge this PR after
your CI passes on it
- `@dependabot cancel merge` will cancel a previously requested merge
and block automerging
- `@dependabot reopen` will reopen this PR if it is closed
- `@dependabot close` will close this PR and stop Dependabot recreating
it. You can achieve the same result by closing it manually
- `@dependabot show <dependency name> ignore conditions` will show all
of the ignore conditions of the specified dependency
- `@dependabot ignore this major version` will close this PR and stop
Dependabot creating any more for this major version (unless you reopen
the PR or upgrade to it yourself)
- `@dependabot ignore this minor version` will close this PR and stop
Dependabot creating any more for this minor version (unless you reopen
the PR or upgrade to it yourself)
- `@dependabot ignore this dependency` will close this PR and stop
Dependabot creating any more for this dependency (unless you reopen the
PR or upgrade to it yourself)


</details>

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-11-24 11:43:57 -08:00
Dylan Hurd
1e832b1438 fix(windows) support apply_patch parsing in powershell (#7221)
## Summary
Support powershell parsing of apply_patch

## Testing
- [x] Enable apply_patch unit tests

---------

Co-authored-by: jif-oai <jif@openai.com>
2025-11-24 19:32:47 +00:00
Matthew Zeng
c31663d745 [feedback] Add source info into feedback metadata. (#7140)
Verified the source info is correctly attached based on whether it's cli
or vscode.
2025-11-24 19:05:37 +00:00
jif-oai
35d89e820f fix: flaky test (#7257) 2025-11-24 18:45:41 +00:00
iceweasel-oai
486b1c4d9d consolidate world-writable-directories scanning. (#7234)
clean up the code for scanning for world writable directories

One path (selecting a sandbox mode from /approvals) was using an
incorrect method that did not use the new method of creating deny aces
to prevent writing to those directories. Now all paths are the same.
2025-11-24 09:51:58 -08:00
jif-oai
b2cddec3d7 feat: unified exec basic pruning strategy (#7239)
LRU + exited sessions first
2025-11-24 17:22:32 +00:00
jif-oai
920239f272 fix: codex delegate cancellation (#7092) 2025-11-24 16:59:09 +00:00
jif-oai
99bcb90353 chore: use proxy for encrypted summary (#7252) 2025-11-24 16:51:47 +00:00
Ahmed Ibrahim
b519267d05 Account for encrypted reasoning for auto compaction (#7113)
- The total token used returned from the api doesn't account for the
reasoning items before the assistant message
- Account for those for auto compaction
- Add the encrypted reasoning effort in the common tests utils
- Add a test to make sure it works as expected
2025-11-22 03:06:45 +00:00
zhao-oai
529eb4ff2a move execpolicy quickstart (#7127) 2025-11-21 19:13:51 -05:00
Michael Bolin
c6f68c9df8 feat: declare server capability in shell-tool-mcp (#7112)
This introduces a new feature to Codex when it operates as an MCP
_client_ where if an MCP _server_ replies that it has an entry named
`"codex/sandbox-state"` in its _server capabilities_, then Codex will
send it an MCP notification with the following structure:

```json
{
  "method": "codex/sandbox-state/update",
  "params": {
    "sandboxPolicy": {
      "type": "workspace-write",
      "network-access": false,
      "exclude-tmpdir-env-var": false
      "exclude-slash-tmp": false
    },
    "codexLinuxSandboxExe": null,
    "sandboxCwd": "/Users/mbolin/code/codex2"
  }
}
```

or with whatever values are appropriate for the initial `sandboxPolicy`.

**NOTE:** Codex _should_ continue to send the MCP server notifications
of the same format if these things change over the lifetime of the
thread, but that isn't wired up yet.

The result is that `shell-tool-mcp` can consume these values so that
when it calls `codex_core::exec::process_exec_tool_call()` in
`codex-rs/exec-server/src/posix/escalate_server.rs`, it is now sure to
call it with the correct values (whereas previously we relied on
hardcoded values).

While I would argue this is a supported use case within the MCP
protocol, the `rmcp` crate that we are using today does not support
custom notifications. As such, I had to patch it and I submitted it for
review, so hopefully it will be accepted in some form:

https://github.com/modelcontextprotocol/rust-sdk/pull/556

To test out this change from end-to-end:

- I ran `cargo build` in `~/code/codex2/codex-rs/exec-server`
- I built the fork of Bash in `~/code/bash/bash`
- I added the following to my `~/.codex/config.toml`:

```toml
# Use with `codex --disable shell_tool`.
[mcp_servers.execshell]
args = ["--bash", "/Users/mbolin/code/bash/bash"]
command = "/Users/mbolin/code/codex2/codex-rs/target/debug/codex-exec-mcp-server"
```

- From `~/code/codex2/codex-rs`, I ran `just codex --disable shell_tool`
- When the TUI started up, I verified that the sandbox mode is
`workspace-write`
- I ran `/mcp` to verify that the shell tool from the MCP is there:

<img width="1387" height="1400" alt="image"
src="https://github.com/user-attachments/assets/1a8addcc-5005-4e16-b59f-95cfd06fd4ab"
/>

- Then I asked it:

> what is the output of `gh issue list`

because this should be auto-approved with our existing dummy policy:


af63e6eccc/codex-rs/exec-server/src/posix.rs (L157-L164)

And it worked:

<img width="1387" height="1400" alt="image"
src="https://github.com/user-attachments/assets/7568d2f7-80da-4d68-86d0-c265a6f5e6c1"
/>
2025-11-21 16:11:01 -08:00
Michael Bolin
af63e6eccc fix: start publishing @openai/codex-shell-tool-mcp to npm (#7123)
Start publishing as part of the normal release process.
2025-11-21 15:03:50 -08:00
zhao-oai
87b211709e bypass sandbox for policy approved commands (#7110)
allowing cmds greenlit by execpolicy to bypass sandbox + minor refactor
for a world where we have execpolicy rules with specific sandbox
requirements
2025-11-21 18:03:23 -05:00
Michael Bolin
67975ed33a refactor: inline sandbox type lookup in process_exec_tool_call (#7122)
`process_exec_tool_call()` was taking `SandboxType` as a param, but in
practice, the only place it was constructed was in
`codex_message_processor.rs` where it was derived from the other
`sandbox_policy` param, so this PR inlines the logic that decides the
`SandboxType` into `process_exec_tool_call()`.



---
[//]: # (BEGIN SAPLING FOOTER)
Stack created with [Sapling](https://sapling-scm.com). Best reviewed
with [ReviewStack](https://reviewstack.dev/openai/codex/pull/7122).
* #7112
* __->__ #7122
2025-11-21 22:53:05 +00:00
Jeremy Rose
7561a6aaf0 support MCP elicitations (#6947)
No support for request schema yet, but we'll at least show the message
and allow accept/decline.

<img width="823" height="551" alt="Screenshot 2025-11-21 at 2 44 05 PM"
src="https://github.com/user-attachments/assets/6fbb892d-ca12-4765-921e-9ac4b217534d"
/>
2025-11-21 14:44:53 -08:00
Josh McKinney
3ea33a0616 fix(tui): Fail when stdin is not a terminal (#6382)
Piping to codex fails to do anything useful and locks up the process.
We currently check for stdout, but not stdin

```
❯ echo foo|just c
cargo run --bin codex -- "$@"
    Finished `dev` profile [unoptimized + debuginfo] target(s) in 0.21s
     Running `target/debug/codex`
Error: stdin is not a terminal
error: Recipe `codex` failed on line 10 with exit code 1
```
2025-11-21 14:17:40 -08:00
Michael Bolin
e8ef6d3c16 feat: support login as an option on shell-tool-mcp (#7120)
The unified exec tool has a `login` option that defaults to `true`:


3bdcbc7292/codex-rs/core/src/tools/handlers/unified_exec.rs (L35-L36)

This updates the `ExecParams` for `shell-tool-mcp` to support the same
parameter. Note it is declared as `Option<bool>` to ensure it is marked
optional in the generated JSON schema.
2025-11-21 22:14:41 +00:00
pakrym-oai
e52cc38dfd Use use_model (#7121) 2025-11-21 22:10:52 +00:00
iceweasel-oai
3bdcbc7292 Windows: flag some invocations that launch browsers/URLs as dangerous (#7111)
Prevent certain Powershell/cmd invocations from reaching the sandbox
when they are trying to launch a browser, or run a command with a URL,
etc.
2025-11-21 13:36:17 -08:00
Owen Lin
a0434bbdb4 [app-server] doc: approvals (#7105)
Add documentation for shell and apply_patch approvals
2025-11-21 21:27:54 +00:00
Ahmed Ibrahim
d5f661c91d enable unified exec for experiments (#7118) 2025-11-21 13:10:01 -08:00
Ahmed Ibrahim
8ecaad948b feat: Add exp model to experiment with the tools (#7115) 2025-11-21 12:44:47 -08:00
Owen Lin
aa4e0d823e [app-server] feat: expose gitInfo/cwd/etc. on Thread (#7060)
Port the new additions from https://github.com/openai/codex/pull/6337 on
the legacy API to v2. Mainly need `gitInfo` and `cwd` for VSCE.
2025-11-21 10:37:12 -08:00
Dylan Hurd
0e051644a9 fix(scripts) next_minor_version should reset patch number (#7050)
## Summary
When incrementing the minor version, we should reset patch to 0, rather
than keeping it.

## Testing
- [x] tested locally with dry_run and `get_latest_release_version`
mocked out

---------

Co-authored-by: Michael Bolin <mbolin@openai.com>
2025-11-21 10:17:12 -08:00
Michael Bolin
40d14c0756 fix: clear out duplicate entries for bash in the GitHub release (#7103)
https://github.com/openai/codex/pull/7005 introduced a new part of the
release process that added multiple files named `bash` in the `dist/`
folder used as the basis of the GitHub Release. I believe that all file
names in a GitHub Release have to be unique, which is why the recent
release build failed:

https://github.com/openai/codex/actions/runs/19577669780/job/56070183504

Based on the output of the **List** step, I believe these are the
appropriate artifacts to delete as a quick fix.
2025-11-21 09:59:30 -08:00
jif-oai
af65666561 chore: drop model_max_output_tokens (#7100) 2025-11-21 17:42:54 +00:00
Owen Lin
2ae1f81d84 [app-server] feat: add Declined status for command exec (#7101)
Add a `Declined` status for when we request an approval from the user
and the user declines. This allows us to distinguish from commands that
actually ran, but failed.

This behaves similarly to apply_patch / FileChange, which does the same
thing.
2025-11-21 09:19:39 -08:00
Michael Bolin
d363a0968e feat: codex-shell-tool-mcp (#7005)
This adds a GitHub workflow for building a new npm module we are
experimenting with that contains an MCP server for running Bash
commands. The new workflow, `shell-tool-mcp`, is a dependency of the
general `release` workflow so that we continue to use one version number
for all artifacts across the project in one GitHub release.

`.github/workflows/shell-tool-mcp.yml` is the primary workflow
introduced by this PR, which does the following:

- builds the `codex-exec-mcp-server` and `codex-execve-wrapper`
executables for both arm64 and x64 versions of Mac and Linux (preferring
the MUSL version for Linux)
- builds Bash (dynamically linked) for a [comically] large number of
platforms (both x64 and arm64 for most) with a small patch specified by
`shell-tool-mcp/patches/bash-exec-wrapper.patch`:
  - `debian-11`
  - `debian-12`
  - `ubuntu-20.04`
  - `ubuntu-22.04`
  - `ubuntu-24.04`
  - `centos-9`
  - `macos-13` (x64 only)
  - `macos-14` (arm64 only)
  - `macos-15` (arm64 only)
- builds the TypeScript for the [new] Node module declared in the
`shell-tool-mcp/` folder, which creates `bin/mcp-server.js`
- adds all of the native binaries to `shell-tool-mcp/vendor/` folder;
`bin/mcp-server.js` does a runtime check to determine which ones to
execute
- uses `npm pack` to create the `.tgz` for the module
- if `publish: true` is set, invokes the `npm publish` call with the
`.tgz`

The justification for building Bash for so many different operating
systems is because, since it is dynamically linked, we want to increase
our confidence that the version we build is compatible with the glibc
whatever OS we end up running on. (Note this is less of a concern with
`codex-exec-mcp-server` and `codex-execve-wrapper` on Linux, as they are
statically linked.)

This PR also introduces the code for the npm module in `shell-tool-mcp/`
(the proposed module name is `@openai/codex-shell-tool-mcp`). Initially,
I intended the module to be a single file of vanilla JavaScript (like
[`codex-cli/bin/codex.js`](ab5972d447/codex-cli/bin/codex.js)),
but some of the logic seemed a bit tricky, so I decided to port it to
TypeScript and add unit tests.

`shell-tool-mcp/src/index.ts` defines the `main()` function for the
module, which performs runtime checks to determine the clang triple to
find the path to the Rust executables within the `vendor/` folder
(`resolveTargetTriple()`). It uses a combination of `readOsRelease()`
and `resolveBashPath()` to determine the correct Bash executable to run
in the environment. Ultimately, it spawns a command like the following:

```
codex-exec-mcp-server \
    --execve codex-execve-wrapper \
    --bash custom-bash "$@"
```

Note `.github/workflows/shell-tool-mcp-ci.yml` defines a fairly standard
CI job for the module (`format`/`build`/`test`).

To test this PR, I pushed this branch to my personal fork of Codex and
ran the CI job there:

https://github.com/bolinfest/codex/actions/runs/19564311320

Admittedly, the graph looks a bit wild now:

<img width="5115" height="2969" alt="Screenshot 2025-11-20 at 11 44
58 PM"
src="https://github.com/user-attachments/assets/cc5ef306-efc1-4ed7-a137-5347e394f393"
/>

But when it finished, I was able to download `codex-shell-tool-mcp-npm`
from the **Artifacts** for the workflow in an empty temp directory,
unzip the `.zip` and then the `.tgz` inside it, followed by `xattr -rc
.` to remove the quarantine bits. Then I ran:

```shell
npx @modelcontextprotocol/inspector node /private/tmp/foobar4/package/bin/mcp-server.js
```

which launched the MCP Inspector and I was able to use it as expected!
This bodes well that this should work once the package is published to
npm:

```shell
npx @modelcontextprotocol/inspector npx @openai/codex-shell-tool-mcp
```

Also, to verify the package contains what I expect:

```shell
/tmp/foobar4/package$ tree
.
├── bin
│   └── mcp-server.js
├── package.json
├── README.md
└── vendor
    ├── aarch64-apple-darwin
    │   ├── bash
    │   │   ├── macos-14
    │   │   │   └── bash
    │   │   └── macos-15
    │   │       └── bash
    │   ├── codex-exec-mcp-server
    │   └── codex-execve-wrapper
    ├── aarch64-unknown-linux-musl
    │   ├── bash
    │   │   ├── centos-9
    │   │   │   └── bash
    │   │   ├── debian-11
    │   │   │   └── bash
    │   │   ├── debian-12
    │   │   │   └── bash
    │   │   ├── ubuntu-20.04
    │   │   │   └── bash
    │   │   ├── ubuntu-22.04
    │   │   │   └── bash
    │   │   └── ubuntu-24.04
    │   │       └── bash
    │   ├── codex-exec-mcp-server
    │   └── codex-execve-wrapper
    ├── x86_64-apple-darwin
    │   ├── bash
    │   │   └── macos-13
    │   │       └── bash
    │   ├── codex-exec-mcp-server
    │   └── codex-execve-wrapper
    └── x86_64-unknown-linux-musl
        ├── bash
        │   ├── centos-9
        │   │   └── bash
        │   ├── debian-11
        │   │   └── bash
        │   ├── debian-12
        │   │   └── bash
        │   ├── ubuntu-20.04
        │   │   └── bash
        │   ├── ubuntu-22.04
        │   │   └── bash
        │   └── ubuntu-24.04
        │       └── bash
        ├── codex-exec-mcp-server
        └── codex-execve-wrapper

26 directories, 26 files
```
2025-11-21 08:16:36 -08:00
jif-oai
bce030ddb5 Revert "fix: read max_output_tokens param from config" (#7088)
Reverts openai/codex#4139
2025-11-21 11:40:02 +01:00
iceweasel-oai
f4af6e389e Windows Sandbox: support network_access and exclude_tmpdir_env_var (#7030) 2025-11-20 22:59:55 -08:00
Eric Traut
b315b22f7b Fixed the deduplicator github action (#7070)
It stopped working (found zero duplicates) starting three days ago when
the model was switched from `gpt-5` to `gpt-5.1`. I'm not sure why it
stopped working. This is an attempt to get it working again by using the
default model for the codex action (which is presumably
`gpt-5.1-codex-max`).
2025-11-20 22:46:55 -08:00
Yorling
c9e149fd5c fix: read max_output_tokens param from config (#4139)
Request param `max_output_tokens` is documented in
`https://github.com/openai/codex/blob/main/docs/config.md`,
but nowhere uses the item in config, this commit read it from config for
GPT responses API.

see https://github.com/openai/codex/issues/4138 for issue report.

Signed-off-by: Yorling <shallowcloud@yeah.net>
2025-11-20 22:46:34 -08:00
Eric Traut
bacdc004be Fixed two tests that can fail in some environments that have global git rewrite rules (#7068)
This fixes https://github.com/openai/codex/issues/7044
2025-11-20 22:45:40 -08:00
pakrym-oai
ab5972d447 Support all types of search actions (#7061)
Fixes the 

```
{
  "error": {
    "message": "Invalid value: 'other'. Supported values are: 'search', 'open_page', and 'find_in_page'.",
    "type": "invalid_request_error",
    "param": "input[150].action.type",
    "code": "invalid_value"
  }
```
error.


The actual-actual fix here is supporting absent `query` parameter.
2025-11-20 20:45:28 -08:00
pakrym-oai
767b66f407 Migrate coverage to shell_command (#7042) 2025-11-21 03:44:00 +00:00
pakrym-oai
830ab4ce20 Support full powershell paths in is_safe_command (#7055)
New shell implementation always uses full paths.
2025-11-20 19:29:15 -08:00
Dylan Hurd
3f73e2c892 fix(app-server) remove www warning (#7046)
### Summary
After #7022, we no longer need this warning. We should also clean up the
schema for the notification, but this is a quick fix to just stop the
behavior in the VSCE

## Testing
- [x] Ran locally
2025-11-20 19:18:39 -08:00
Dylan Hurd
1822ffe870 feat(tui): default reasoning selection to medium (#7040)
## Summary
- allow selection popups to request an initial highlighted row
- begin the /models reasoning selector focused on the default effort

## Testing
- just fmt
- just fix -p codex-tui
- cargo test -p codex-tui



https://github.com/user-attachments/assets/b322aeb1-e8f3-4578-92f7-5c2fa5ee4c98



------
[Codex
Task](https://chatgpt.com/codex/tasks/task_i_691f75e8fc188322a910fbe2138666ef)
2025-11-20 17:06:04 -08:00
Celia Chen
7e2165f394 [app-server] update doc with codex error info (#6941)
Document new codex error info. Also fixed the name from
`codex_error_code` to `codex_error_info`.
2025-11-21 01:02:37 +00:00
Michael Bolin
8e5f38c0f0 feat: waiting for an elicitation should not count against a shell tool timeout (#6973)
Previously, we were running into an issue where we would run the `shell`
tool call with a timeout of 10s, but it fired an elicitation asking for
user approval, the time the user took to respond to the elicitation was
counted agains the 10s timeout, so the `shell` tool call would fail with
a timeout error unless the user is very fast!

This PR addresses this issue by introducing a "stopwatch" abstraction
that is used to manage the timeout. The idea is:

- `Stopwatch::new()` is called with the _real_ timeout of the `shell`
tool call.
- `process_exec_tool_call()` is called with the `Cancellation` variant
of `ExecExpiration` because it should not manage its own timeout in this
case
- the `Stopwatch` expiration is wired up to the `cancel_rx` passed to
`process_exec_tool_call()`
- when an elicitation for the `shell` tool call is received, the
`Stopwatch` pauses
- because it is possible for multiple elicitations to arrive
concurrently, it keeps track of the number of "active pauses" and does
not resume until that counter goes down to zero

I verified that I can test the MCP server using
`@modelcontextprotocol/inspector` and specify `git status` as the
`command` with a timeout of 500ms and that the elicitation pops up and I
have all the time in the world to respond whereas previous to this PR,
that would not have been possible.

---
[//]: # (BEGIN SAPLING FOOTER)
Stack created with [Sapling](https://sapling-scm.com). Best reviewed
with [ReviewStack](https://reviewstack.dev/openai/codex/pull/6973).
* #7005
* __->__ #6973
* #6972
2025-11-20 16:45:38 -08:00
Ahmed Ibrahim
1388e99674 fix flaky tool_call_output_exceeds_limit_truncated_chars_limit (#7043)
I am suspecting this is flaky because of the wall time can become 0,
0.1, or 1.
2025-11-20 16:36:29 -08:00
Michael Bolin
f56d1dc8fc feat: update process_exec_tool_call() to take a cancellation token (#6972)
This updates `ExecParams` so that instead of taking `timeout_ms:
Option<u64>`, it now takes a more general cancellation mechanism,
`ExecExpiration`, which is an enum that includes a
`Cancellation(tokio_util::sync::CancellationToken)` variant.

If the cancellation token is fired, then `process_exec_tool_call()`
returns in the same way as if a timeout was exceeded.

This is necessary so that in #6973, we can manage the timeout logic
external to the `process_exec_tool_call()` because we want to "suspend"
the timeout when an elicitation from a human user is pending.








---
[//]: # (BEGIN SAPLING FOOTER)
Stack created with [Sapling](https://sapling-scm.com). Best reviewed
with [ReviewStack](https://reviewstack.dev/openai/codex/pull/6972).
* #7005
* #6973
* __->__ #6972
2025-11-20 16:29:57 -08:00
Ahmed Ibrahim
9be310041b migrate collect_tool_identifiers_for_model to test_codex (#7041)
Maybe it solved flakiness
2025-11-20 16:02:50 -08:00
Xiao-Yong Jin
0fbcdd77c8 core: make shell behavior portable on FreeBSD (#7039)
- Use /bin/sh instead of /bin/bash on FreeBSD/OpenBSD in the process
group timeout test to avoid command-not-found failures.

- Accept /usr/local/bin/bash as a valid SHELL path to match common
FreeBSD installations.

- Switch the shell serialization duration test to /bin/sh for improved
portability across Unix platforms.

With this change, `cargo test -p codex-core --lib` runs and passes on
FreeBSD.
2025-11-20 16:01:35 -08:00
Celia Chen
9bce050385 [app-server & core] introduce new codex error code and v2 app-server error events (#6938)
This PR does two things:
1. populate a new `codex_error_code` protocol in error events sent from
core to client;
2. old v1 core events `codex/event/stream_error` and `codex/event/error`
will now both become `error`. We also show codex error code for
turncompleted -> error status.

new events in app server test:
```
< {
<   "method": "codex/event/stream_error",
<   "params": {
<     "conversationId": "019aa34c-0c14-70e0-9706-98520a760d67",
<     "id": "0",
<     "msg": {
<       "codex_error_code": {
<         "response_stream_disconnected": {
<           "http_status_code": 401
<         }
<       },
<       "message": "Reconnecting... 2/5",
<       "type": "stream_error"
<     }
<   }
< }

 {
<   "method": "error",
<   "params": {
<     "error": {
<       "codexErrorCode": {
<         "responseStreamDisconnected": {
<           "httpStatusCode": 401
<         }
<       },
<       "message": "Reconnecting... 2/5"
<     }
<   }
< }

< {
<   "method": "turn/completed",
<   "params": {
<     "turn": {
<       "error": {
<         "codexErrorCode": {
<           "responseTooManyFailedAttempts": {
<             "httpStatusCode": 401
<           }
<         },
<         "message": "exceeded retry limit, last status: 401 Unauthorized, request id: 9a1b495a1a97ed3e-SJC"
<       },
<       "id": "0",
<       "items": [],
<       "status": "failed"
<     }
<   }
< }
```
2025-11-20 23:06:55 +00:00
iceweasel-oai
3f92ad4190 add deny ACEs for world writable dirs (#7022)
Our Restricted Token contains 3 SIDs (Logon, Everyone, {WorkspaceWrite
Capability || ReadOnly Capability})

because it must include Everyone, that left us vulnerable to directories
that allow writes to Everyone. Even though those directories do not have
ACEs that enable our capability SIDs to write to them, they could still
be written to even in ReadOnly mode, or even in WorkspaceWrite mode if
they are outside of a writable root.

A solution to this is to explicitly add *Deny* ACEs to these
directories, always for the ReadOnly Capability SID, and for the
WorkspaceWrite SID if the directory is outside of a workspace root.

Under a restricted token, Windows always checks Deny ACEs before Allow
ACEs so even though our restricted token would allow a write to these
directories due to the Everyone SID, it fails first because of the Deny
ACE on the capability SID
2025-11-20 14:50:33 -08:00
Ahmed Ibrahim
54ee302a06 Attempt to fix unified_exec_formats_large_output_summary flakiness (#7029)
second attempt to fix this test after
https://github.com/openai/codex/pull/6884. I think this flakiness is
happening because yield_time is too small for a 10,000 step loop in
python.
2025-11-20 14:38:04 -08:00
Ahmed Ibrahim
44fa06ae36 fix flaky test: approval_matrix_covers_all_modes (#7028)
looks like it sometimes flake around 30. let's give it more time.
2025-11-20 14:37:42 -08:00
pakrym-oai
856f97f449 Delete shell_command feature (#7024) 2025-11-20 14:14:56 -08:00
zhao-oai
fe7a3f0c2b execpolicycheck command in codex cli (#7012)
adding execpolicycheck tool onto codex cli

this is useful for validating policies (can be multiple) against
commands.

it will also surface errors in policy syntax:
<img width="1150" height="281" alt="Screenshot 2025-11-19 at 12 46
21 PM"
src="https://github.com/user-attachments/assets/8f99b403-564c-4172-acc9-6574a8d13dc3"
/>

this PR also changes output format when there's no match in the CLI.
instead of returning the raw string `noMatch`, we return
`{"noMatch":{}}`

this PR is a rewrite of: https://github.com/openai/codex/pull/6932 (due
to the numerous merge conflicts present in the original PR)

---------

Co-authored-by: Michael Bolin <mbolin@openai.com>
2025-11-20 16:44:31 -05:00
zhao-oai
c30ca0d5b6 increasing user shell timeout to 1 hour (#7025)
setting user shell timeout to an unreasonably high value since there
isn't an easy way to have a command run without timeouts

currently, user shell commands timeout is 10 seconds
2025-11-20 13:39:16 -08:00
Weiller Carvalho
a8a6cbdd1c fix: route feedback issue links by category (#6840)
## Summary
- TUI feedback note now only links to the bug-report template when the
category is bug/bad result.
- Good result/other feedback shows a thank-you+thread ID instead of
funneling people to file a bug.
- Added a helper + unit test so future changes keep the behavior
consistent.

## Testing
  - just fmt
  - just fix -p codex-tui
  - cargo test -p codex-tui

  Fixes #6839
2025-11-20 13:20:03 -08:00
Dmitri Khokhlov
e4257f432e codex-exec: allow resume --last to read prompt #6717 (#6719)
### Description

- codex exec --json resume --last "<prompt>" bailed out because clap
treated the prompt as SESSION_ID. I removed the conflicts_with flag and
reinterpret that positional as a prompt when
--last is set, so the flow now keeps working in JSON mode.
(codex-rs/exec/src/cli.rs:84-104, codex-rs/exec/src/lib.rs:75-130)
- Added a regression test that exercises resume --last in JSON mode to
ensure the prompt is accepted and the rollout file is updated.
(codex-rs/exec/tests/suite/resume.rs:126-178)

### Testing

  - just fmt
  - cargo test -p codex-exec
  - just fix -p codex-exec
  - cargo test -p codex-exec

#6717

Signed-off-by: Dmitri Khokhlov <dkhokhlov@cribl.io>
2025-11-20 13:10:49 -08:00
Jeremy Rose
2c793083f4 tui: centralize markdown styling and make inline code cyan (#7023)
<img width="762" height="271" alt="Screenshot 2025-11-20 at 12 54 06 PM"
src="https://github.com/user-attachments/assets/10021d63-27eb-407b-8fcc-43740e3bfb0f"
/>
2025-11-20 21:06:22 +00:00
Lionel Cheng
e150798baf Bumped number of fuzzy search results from 8 to 20 (#7013)
I just noticed that in the VSCode / Codex extension when you type @ the
number of results is around 70:

- small video of searching for `mod.rs` inside `codex` repository:
https://github.com/user-attachments/assets/46e53d31-adff-465e-b32b-051c4c1c298c

- while in the CLI the number of results is currently of 8 which is
quite small:
<img width="615" height="439" alt="Screenshot 2025-11-20 at 09 42 04"
src="https://github.com/user-attachments/assets/1c6d12cb-3b1f-4d5b-9ad3-6b12975eaaec"
/>

I bumped it to 20. I had several cases where I wanted a file and did not
find it because the number of results was too small

Signed-off-by: lionel-oai <lionel@openai.com>
Co-authored-by: lionel-oai <lionel@openai.com>
2025-11-20 12:33:12 -08:00
Kyuheon Kim
33a6cc66ab fix(cli): correct mcp add usage order (#6827)
## Summary
- add an explicit `override_usage` string to `AddArgs` so clap prints
`<NAME>` before the command/url choice, matching the actual parser and
docs

### Before

Usage: codex mcp add [OPTIONS] <COMMAND|--url <URL>> <NAME>


### After

Usage: codex mcp add [OPTIONS] <NAME> [--url <URL> | -- <COMMAND>...]

---------

Signed-off-by: kyuheon-kr <kyuheon.kr@gmail.com>
2025-11-20 12:32:12 -08:00
pakrym-oai
52d0ec4cd8 Delete tiktoken-rs (#7018) 2025-11-20 11:15:04 -08:00
LIHUA
397279d46e Fix: Improve text encoding for shell output in VSCode preview (#6178) (#6182)
## 🐛 Problem

Users running commands with non-ASCII characters (like Russian text
"пример") in Windows/WSL environments experience garbled text in
VSCode's shell preview window, with Unicode replacement characters (�)
appearing instead of the actual text.

**Issue**: https://github.com/openai/codex/issues/6178

## 🔧 Root Cause

The issue was in `StreamOutput<Vec<u8>>::from_utf8_lossy()` method in
`codex-rs/core/src/exec.rs`, which used `String::from_utf8_lossy()` to
convert shell output bytes to strings. This function immediately
replaces any invalid UTF-8 byte sequences with replacement characters,
without attempting to decode using other common encodings.

In Windows/WSL environments, shell output often uses encodings like:

- Windows-1252 (common Windows encoding)
- Latin-1/ISO-8859-1 (extended ASCII)

## 🛠️ Solution

Replaced the simple `String::from_utf8_lossy()` call with intelligent
encoding detection via a new `bytes_to_string_smart()` function that
tries multiple encoding strategies:

1. **UTF-8** (fast path for valid UTF-8)
2. **Windows-1252** (handles Windows-specific characters in 0x80-0x9F
range)
3. **Latin-1** (fallback for extended ASCII)
4. **Lossy UTF-8** (final fallback, same as before)

## 📁 Changes

### New Files

- `codex-rs/core/src/text_encoding.rs` - Smart encoding detection module
- `codex-rs/core/tests/suite/text_encoding_fix.rs` - Integration tests

### Modified Files

- `codex-rs/core/src/lib.rs` - Added text_encoding module
- `codex-rs/core/src/exec.rs` - Updated StreamOutput::from_utf8_lossy()
- `codex-rs/core/tests/suite/mod.rs` - Registered new test module

##  Testing

- **5 unit tests** covering UTF-8, Windows-1252, Latin-1, and fallback
scenarios
- **2 integration tests** simulating the exact Issue #6178 scenario
- **Demonstrates improvement** over the previous
`String::from_utf8_lossy()` approach

All tests pass:

```bash
cargo test -p codex-core text_encoding
cargo test -p codex-core test_shell_output_encoding_issue_6178
```

## 🎯 Impact

-  **Eliminates garbled text** in VSCode shell preview for non-ASCII
content
-  **Supports Windows/WSL environments** with proper encoding detection
-  **Zero performance impact** for UTF-8 text (fast path)
-  **Backward compatible** - UTF-8 content works exactly as before
-  **Handles edge cases** with robust fallback mechanism

## 🧪 Test Scenarios

The fix has been tested with:

- Russian text ("пример")
- Windows-1252 quotation marks (""test")
- Latin-1 accented characters ("café")
- Mixed encoding content
- Invalid byte sequences (graceful fallback)

## 📋 Checklist

- [X] Addresses the reported issue
- [X] Includes comprehensive tests
- [X] Maintains backward compatibility
- [X] Follows project coding conventions
- [X] No breaking changes

---------

Co-authored-by: Josh McKinney <joshka@openai.com>
2025-11-20 11:04:11 -08:00
pakrym-oai
30ca89424c Always fallback to real shell (#6953)
Either cmd.exe or `/bin/sh`.
2025-11-20 10:58:46 -08:00
Eric Traut
d909048a85 Added feature switch to disable animations in TUI (#6870)
This PR adds support for a new feature flag `tui.animations`. By
default, the TUI uses animations in its welcome screen, "working"
spinners, and "shimmer" effects. This animations can interfere with
screen readers, so it's good to provide a way to disable them.

This change is inspired by [a
PR](https://github.com/openai/codex/pull/4014) contributed by @Orinks.
That PR has faltered a bit, but I think the core idea is sound. This
version incorporates feedback from @aibrahim-oai. In particular:
1. It uses a feature flag (`tui.animations`) rather than the unqualified
CLI key `no-animations`. Feature flags are the preferred way to expose
boolean switches. They are also exposed via CLI command switches.
2. It includes more complete documentation.
3. It disables a few animations that the other PR omitted.
2025-11-20 10:40:08 -08:00
jif-oai
888c6dd9e7 fix: command formatting for user commands (#7002) 2025-11-20 17:29:15 +01:00
hanson-openai
b5dd189067 Allow unified_exec to early exit (if the process terminates before yield_time_ms) (#6867)
Thread through an `exit_notify` tokio `Notify` through to the
`UnifiedExecSession` so that we can return early if the command
terminates before `yield_time_ms`.

As Codex review correctly pointed out below 🙌 we also need a
`exit_signaled` flag so that commands which finish before we start
waiting can also exit early.

Since the default `yield_time_ms` is now 10s, this means that we don't
have to wait 10s for trivial commands like ls, sed, etc (which are the
majority of agent commands 😅)

---------

Co-authored-by: jif-oai <jif@openai.com>
2025-11-20 13:34:41 +01:00
Michael Bolin
54e6e4ac32 fix: when displaying execv, show file instead of arg0 (#6966)
After merging https://github.com/openai/codex/pull/6958, I realized that
the `command` I was displaying was not quite right. Since we know it, we
should show the _exact_ program being executed (the first arg to
`execve(3)`) rather than `arg0` to be more precise.

Below is the same command I used to test
https://github.com/openai/codex/pull/6958, but now you can see it shows
`/Users/mbolin/.openai/bin/git` instead of just `git`.

<img width="1526" height="1444" alt="image"
src="https://github.com/user-attachments/assets/428128d1-c658-456e-a64e-fc6a0009cb34"
/>
2025-11-19 22:42:58 -08:00
Michael Bolin
e8af41de8a fix: clean up elicitation used by exec-server (#6958)
Using appropriate message/title fields, I think this looks better now:

<img width="3370" height="3208" alt="image"
src="https://github.com/user-attachments/assets/e9bbf906-4ba8-4563-affc-62cdc6c97342"
/>

Though note that in the current version of the Inspector (`0.17.2`), you
cannot hit **Submit** until you fill out the field. I believe this is a
bug in the Inspector, as it does not properly handle the case when all
fields are optional. I put up a fix:

https://github.com/modelcontextprotocol/inspector/pull/926
2025-11-20 04:59:17 +00:00
Owen Lin
d6c30ed25e [app-server] feat: v2 apply_patch approval flow (#6760)
This PR adds the API V2 version of the apply_patch approval flow, which
centers around `ThreadItem::FileChange`.

This PR wires the new RPC (`item/fileChange/requestApproval`, V2 only)
and related events (`item/started`, `item/completed` for
`ThreadItem::FileChange`, which are emitted in both V1 and V2) through
the app-server
protocol. The new approval RPC is only sent when the user initiates a
turn with the new `turn/start` API so we don't break backwards
compatibility with VSCE.

Similar to https://github.com/openai/codex/pull/6758, the approach I
took was to make as few changes to the Codex core as possible,
leveraging existing `EventMsg` core events, and translating those in
app-server. I did have to add a few additional fields to
`EventMsg::PatchApplyBegin` and `EventMsg::PatchApplyEnd`, but those
were fairly lightweight.

However, the `EventMsg`s emitted by core are the following:
```
1) Auto-approved (no request for approval)

- EventMsg::PatchApplyBegin
- EventMsg::PatchApplyEnd

2) Approved by user
- EventMsg::ApplyPatchApprovalRequest
- EventMsg::PatchApplyBegin
- EventMsg::PatchApplyEnd

3) Declined by user
- EventMsg::ApplyPatchApprovalRequest
- EventMsg::PatchApplyBegin
- EventMsg::PatchApplyEnd
```

For a request triggering an approval, this would result in:
```
item/fileChange/requestApproval
item/started
item/completed
```

which is different from the `ThreadItem::CommandExecution` flow
introduced in https://github.com/openai/codex/pull/6758, which does the
below and is preferable:
```
item/started
item/commandExecution/requestApproval
item/completed
```

To fix this, we leverage `TurnSummaryStore` on codex_message_processor
to store a little bit of state, allowing us to fire `item/started` and
`item/fileChange/requestApproval` whenever we receive the underlying
`EventMsg::ApplyPatchApprovalRequest`, and no-oping when we receive the
`EventMsg::PatchApplyBegin` later.

This is much less invasive than modifying the order of EventMsg within
core (I tried).

The resulting payloads:
```
{
  "method": "item/started",
  "params": {
    "item": {
      "changes": [
        {
          "diff": "Hello from Codex!\n",
          "kind": "add",
          "path": "/Users/owen/repos/codex/codex-rs/APPROVAL_DEMO.txt"
        }
      ],
      "id": "call_Nxnwj7B3YXigfV6Mwh03d686",
      "status": "inProgress",
      "type": "fileChange"
    }
  }
}
```

```
{
  "id": 0,
  "method": "item/fileChange/requestApproval",
  "params": {
    "grantRoot": null,
    "itemId": "call_Nxnwj7B3YXigfV6Mwh03d686",
    "reason": null,
    "threadId": "019a9e11-8295-7883-a283-779e06502c6f",
    "turnId": "1"
  }
}
```

```
{
  "id": 0,
  "result": {
    "decision": "accept"
  }
}
```

```
{
  "method": "item/completed",
  "params": {
    "item": {
      "changes": [
        {
          "diff": "Hello from Codex!\n",
          "kind": "add",
          "path": "/Users/owen/repos/codex/codex-rs/APPROVAL_DEMO.txt"
        }
      ],
      "id": "call_Nxnwj7B3YXigfV6Mwh03d686",
      "status": "completed",
      "type": "fileChange"
    }
  }
}
```
2025-11-19 20:13:31 -08:00
zhao-oai
fb9849e1e3 migrating execpolicy -> execpolicy-legacy and execpolicy2 -> execpolicy (#6956) 2025-11-19 19:14:10 -08:00
Celia Chen
72a1453ac5 Revert "[core] add optional status_code to error events (#6865)" (#6955)
This reverts commit c2ec477d93.

# External (non-OpenAI) Pull Request Requirements

Before opening this Pull Request, please read the dedicated
"Contributing" markdown file or your PR may be closed:
https://github.com/openai/codex/blob/main/docs/contributing.md

If your PR conforms to our contribution guidelines, replace this text
with a detailed and high quality description of your changes.

Include a link to a bug report or enhancement request.
2025-11-20 01:26:14 +00:00
Ahmed Ibrahim
6d67b8b283 stop model migration screen after first time. (#6954)
it got serialized wrong.
2025-11-19 17:17:04 -08:00
zhao-oai
74a75679d9 update execpolicy quickstart readme (#6952) 2025-11-19 16:57:27 -08:00
pakrym-oai
92e3046733 Single pass truncation (#6914) 2025-11-19 16:56:37 -08:00
zhao-oai
65c13f1ae7 execpolicy2 core integration (#6641)
This PR threads execpolicy2 into codex-core.

activated via feature flag: exec_policy (on by default)

reads and parses all .codexpolicy files in `codex_home/codex`

refactored tool runtime API to integrate execpolicy logic

---------

Co-authored-by: Michael Bolin <mbolin@openai.com>
2025-11-19 16:50:43 -08:00
Dylan Hurd
b00a7cf40d fix(shell) fallback shells (#6948)
## Summary
Add fallbacks when user_shell_path does not resolve to a known shell
type

## Testing
- [x] Tests still pass
2025-11-19 16:41:38 -08:00
Michael Bolin
13d378f2ce chore: refactor exec-server to prepare it for standalone MCP use (#6944)
This PR reorganizes things slightly so that:

- Instead of a single multitool executable, `codex-exec-server`, we now
have two executables:
  - `codex-exec-mcp-server` to launch the MCP server
- `codex-execve-wrapper` is the `execve(2)` wrapper to use with the
`BASH_EXEC_WRAPPER` environment variable
- `BASH_EXEC_WRAPPER` must be a single executable: it cannot be a
command string composed of an executable with args (i.e., it no longer
adds the `escalate` subcommand, as before)
- `codex-exec-mcp-server` takes `--bash` and `--execve` as options.
Though if `--execve` is not specified, the MCP server will check the
directory containing `std::env::current_exe()` and attempt to use the
file named `codex-execve-wrapper` within it. In development, this works
out since these executables are side-by-side in the `target/debug`
folder.

With respect to testing, this also fixes an important bug in
`dummy_exec_policy()`, as I was using `ends_with()` as if it applied to
a `String`, but in this case, it is used with a `&Path`, so the
semantics are slightly different.

Putting this all together, I was able to test this by running the
following:

```
~/code/codex/codex-rs$ npx @modelcontextprotocol/inspector \
    ./target/debug/codex-exec-mcp-server --bash ~/code/bash/bash
```

If I try to run `git status` in `/Users/mbolin/code/codex` via the
`shell` tool from the MCP server:

<img width="1589" height="1335" alt="image"
src="https://github.com/user-attachments/assets/9db6aea8-7fbc-4675-8b1f-ec446685d6c4"
/>

then I get prompted with the following elicitation, as expected:

<img width="1589" height="1335" alt="image"
src="https://github.com/user-attachments/assets/21b68fe0-494d-4562-9bad-0ddc55fc846d"
/>

Though a current limitation is that the `shell` tool defaults to a
timeout of 10s, which means I only have 10s to respond to the
elicitation. Ideally, the time spent waiting for a response from a human
should not count against the timeout for the command execution. I will
address this in a subsequent PR.

---

Note `~/code/bash/bash` was created by doing:

```
cd ~/code
git clone https://github.com/bminor/bash
cd bash
git checkout a8a1c2fac029404d3f42cd39f5a20f24b6e4fe4b
<apply the patch below>
./configure
make
```

The patch:

```
diff --git a/execute_cmd.c b/execute_cmd.c
index 070f5119..d20ad2b9 100644
--- a/execute_cmd.c
+++ b/execute_cmd.c
@@ -6129,6 +6129,19 @@ shell_execve (char *command, char **args, char **env)
   char sample[HASH_BANG_BUFSIZ];
   size_t larray;

+  char* exec_wrapper = getenv("BASH_EXEC_WRAPPER");
+  if (exec_wrapper && *exec_wrapper && !whitespace (*exec_wrapper))
+    {
+      char *orig_command = command;
+
+      larray = strvec_len (args);
+
+      memmove (args + 2, args, (++larray) * sizeof (char *));
+      args[0] = exec_wrapper;
+      args[1] = orig_command;
+      command = exec_wrapper;
+    }
+
```
2025-11-19 16:38:14 -08:00
Lionel Cheng
a6597a9958 Fix/correct reasoning display (#6749)
This closes #6748 by implementing fallback to
`model_family.default_reasoning_effort` in `reasoning_effort` display of
`/status` when no `model_reasoning_effort` is set in the configuration.

## common/src/config_summary.rs

- `create_config_summary_entries` now fills the "reasoning effort" entry
with the explicit `config.model_reasoning_effort` when present and falls
back to `config.model_family.default_reasoning_effort` when it is
`None`, instead of emitting the literal string `none`.
- This ensures downstream consumers such as `tui/src/status/helpers.rs`
continue to work unchanged while automatically picking up model-family
defaults when the user has not selected a reasoning effort.

## tui/src/status/helpers.rs / core/src/model_family.rs

`ModelFamily::default_reasoning_effort` metadata is set to `medium` for
both `gpt-5*-codex` and `gpt-5` models following the default behaviour
of the API and recommendation of the codebase:
- per https://platform.openai.com/docs/api-reference/responses/create
`gpt-5` defaults to `medium` reasoning when no preset is passed
- there is no mention of the preset for `gpt-5.1-codex` in the API docs
but `medium` is the default setting for `gpt-5.1-codex` as per
`codex-rs/tui/src/chatwidget/snapshots/codex_tui__chatwidget__tests__model_reasoning_selection_popup.snap`

---------

Signed-off-by: lionelchg <lionel.cheng@hotmail.fr>
Co-authored-by: Eric Traut <etraut@openai.com>
2025-11-19 15:52:24 -08:00
Beehive Innovations
692989c277 fix(context left after review): review footer context after /review (#5610)
## Summary
- show live review token usage while `/review` runs and restore the main
session indicator afterward
  - add regression coverage for the footer behavior

## Testing
  - just fmt
  - cargo test -p codex-tui

Fixes #5604

---------

Signed-off-by: Fahad <fahad@2doapp.com>
2025-11-19 22:50:07 +00:00
iceweasel-oai
2fde03b4a0 stop over-reporting world-writable directories (#6936)
Fix world-writable audit false positives by expanding generic
permissions with MapGenericMask and then checking only concrete write
bits. The earlier check looked for FILE_GENERIC_WRITE/generic masks
directly, which shares bits with read permissions and could flag an
Everyone read ACE as writable.
2025-11-19 13:59:17 -08:00
Michael Bolin
056c8f8279 fix: prepare ExecPolicy in exec-server for execpolicy2 cutover (#6888)
This PR introduces an extra layer of abstraction to prepare us for the
migration to execpolicy2:

- introduces a new trait, `EscalationPolicy`, whose `determine_action()`
method is responsible for producing the `EscalateAction`
- the existing `ExecPolicy` typedef is changed to return an intermediate
`ExecPolicyOutcome` instead of `EscalateAction`
- the default implementation of `EscalationPolicy`,
`McpEscalationPolicy`, composes `ExecPolicy`
- the `ExecPolicyOutcome` includes `codex_execpolicy2::Decision`, which
has a `Prompt` variant
- when `McpEscalationPolicy` gets `Decision::Prompt` back from
`ExecPolicy`, it prompts the user via an MCP elicitation and maps the
result into an `ElicitationAction`
- now that the end user can reply to an elicitation with `Decline` or
`Cancel`, we introduce a new variant, `EscalateAction::Deny`, which the
client handles by returning exit code `1` without running anything

Note the way the elicitation is created is still not quite right, but I
will fix that once we have things running end-to-end for real in a
follow-up PR.
2025-11-19 13:55:29 -08:00
Celia Chen
c2ec477d93 [core] add optional status_code to error events (#6865)
We want to better uncover error status code for clients. Add an optional
status_code to error events (thread error, error, stream error) so app
server could uncover the status code from the client side later.

in event log:
```
< {
<   "method": "codex/event/stream_error",
<   "params": {
<     "conversationId": "019a9a32-f576-7292-9711-8e57e8063536",
<     "id": "0",
<     "msg": {
<       "message": "Reconnecting... 5/5",
<       "status_code": 401,
<       "type": "stream_error"
<     }
<   }
< }
< {
<   "method": "codex/event/error",
<   "params": {
<     "conversationId": "019a9a32-f576-7292-9711-8e57e8063536",
<     "id": "0",
<     "msg": {
<       "message": "exceeded retry limit, last status: 401 Unauthorized, request id: 9a0cb03a485067f7-SJC",
<       "status_code": 401,
<       "type": "error"
<     }
<   }
< }
```
2025-11-19 19:51:21 +00:00
Dylan Hurd
20982d5c6a fix(app-server) move windows world writable warning (#6916)
## Summary
Move the app-server warning into the process_new_conversation

## Testing
- [x] Tested locally
2025-11-19 11:24:49 -08:00
pakrym-oai
64ae9aa3c3 Keep gpt-5.1-codex the default (#6922) 2025-11-19 11:08:10 -08:00
zhao-oai
72af589398 storing credits (#6858)
Expand the rate-limit cache/TUI: store credit snapshots alongside
primary and secondary windows, render “Credits” when the backend reports
they exist (unlimited vs rounded integer balances)
2025-11-19 10:49:35 -08:00
iceweasel-oai
b3d320433f have world_writable_warning_details accept cwd as a param (#6913)
this enables app-server to pass in the correct workspace cwd for the
current conversation
2025-11-19 10:10:03 -08:00
jif-oai
91a1d20e2d use another prompt (#6912) 2025-11-19 17:47:47 +00:00
jif-oai
87716e7cd0 NITs (#6911) 2025-11-19 17:43:51 +00:00
jif-oai
8976551f0d Fix ordering 2 (#6910) 2025-11-19 17:40:27 +00:00
jif-oai
f1d6767685 fix: ordering (#6909) 2025-11-19 17:39:07 +00:00
Ahmed Ibrahim
d62cab9a06 fix: don't truncate at new lines (#6907) 2025-11-19 17:05:48 +00:00
Ahmed Ibrahim
d5dfba2509 feat: arcticfox in the wild (#6906)
<img width="485" height="600" alt="image"
src="https://github.com/user-attachments/assets/4341740d-dd58-4a3e-b69a-33a3be0606c5"
/>

---------

Co-authored-by: jif-oai <jif@openai.com>
2025-11-19 16:31:06 +00:00
Owen Lin
1924500250 [app-server] populate thread>turns>items on thread/resume (#6848)
This PR allows clients to render historical messages when resuming a
thread via `thread/resume` by reading from the list of `EventMsg`
payloads loaded from the rollout, and then transforming them into Turns
and ThreadItems to be returned on the `Thread` object.

This is implemented by leveraging `SessionConfiguredNotification` which
returns this list of `EventMsg` objects when resuming a conversation,
and then applying a stateful `ThreadHistoryBuilder` that parses from
this EventMsg log and transforms it into Turns and ThreadItems.

Note that we only persist a subset of `EventMsg`s in a rollout as
defined in `policy.rs`, so we lose fidelity whenever we resume a thread
compared to when we streamed the thread's turns originally. However,
this behavior is at parity with the legacy API.
2025-11-19 15:58:09 +00:00
jif-oai
cfc57e14c7 nit: useless log to debug (#6898)
When you type too fast in most terminals, it gets interpreted as paste,
making this log spam
2025-11-19 12:32:53 +00:00
Dylan Hurd
15b5eb30ed fix(core) Support changing /approvals before conversation (#6836)
## Summary
Setting `/approvals` before the start of a conversation was not updating
the environment_context for a conversation. Not sure exactly when this
problem was introduced, but this should reduce model confusion
dramatically.

## Testing
- [x] Added unit test to reproduce bug, confirmed fix with update
- [x] Tested locally
2025-11-19 11:32:48 +00:00
jif-oai
3e9e1d993d chore: consolidate compaction token usage (#6894) 2025-11-19 11:26:01 +00:00
Dylan Hurd
44c747837a chore(app-server) world-writable windows notification (#6880)
## Summary
On app-server startup, detect whether the experimental sandbox is
enabled, and send a notification .

**Note**
New conversations will not respect the feature because we [ignore cli
overrides in
NewConversation](a75321a64c/codex-rs/app-server/src/codex_message_processor.rs (L1237-L1252)).
However, this should be okay, since we don't actually use config for
this, we use a [global
variable](87cce88f48/codex-rs/core/src/safety.rs (L105-L110)).
We should carefully unwind this setup at some point.


## Testing
- [ ] In progress: testing locally

---------

Co-authored-by: jif-oai <jif@openai.com>
2025-11-19 11:19:34 +00:00
jif-oai
4985a7a444 fix: parallel tool call instruction injection (#6893) 2025-11-19 11:01:57 +00:00
jif-oai
10d571f236 nit: stable (#6895) 2025-11-19 10:43:43 +00:00
jif-oai
956d3bfac6 feat: warning large commits (#6838) 2025-11-19 10:22:10 +00:00
Thibault Sottiaux
73488657cb fix label (#6892) 2025-11-19 10:11:30 +00:00
Ahmed Ibrahim
efebc62fb7 Move shell to use truncate_text (#6842)
Move shell to use the configurable `truncate_text`

---------

Co-authored-by: pakrym-oai <pakrym@openai.com>
2025-11-19 01:56:08 -08:00
pakrym-oai
75f38f16dd Run remote auto compaction (#6879) 2025-11-19 00:43:58 -08:00
Ahmed Ibrahim
0440a3f105 flaky-unified_exec_formats_large_output_summary (#6884)
# External (non-OpenAI) Pull Request Requirements

Before opening this Pull Request, please read the dedicated
"Contributing" markdown file or your PR may be closed:
https://github.com/openai/codex/blob/main/docs/contributing.md

If your PR conforms to our contribution guidelines, replace this text
with a detailed and high quality description of your changes.

Include a link to a bug report or enhancement request.
2025-11-19 00:00:37 -08:00
pakrym-oai
ee0484a98c shell_command returns freeform output (#6860)
Instead of returning structured out and then re-formatting it into
freeform, return the freeform output from shell_command tool.

Keep `shell` as the default tool for GPT-5.
2025-11-18 23:38:43 -08:00
Dylan Hurd
7e0e675db4 chore(core) arcticfox (#6876)
..
2025-11-18 23:38:08 -08:00
Dylan Hurd
84458f12f6 fix(tui) ghost snapshot notifications (#6881)
## Summary
- avoid surfacing ghost snapshot warnings in the TUI when snapshot
creation fails, logging the conditions instead
- continue to capture successful ghost snapshots without changing
existing behavior

## Testing
- `cargo test -p codex-core` *(fails:
default_client::tests::test_create_client_sets_default_headers,
default_client::tests::test_get_codex_user_agent,
exec::tests::kill_child_process_group_kills_grandchildren_on_timeout)*

------
[Codex
Task](https://chatgpt.com/codex/tasks/task_i_691c02238db08322927c47b8c2d72c4c)
2025-11-18 23:23:00 -08:00
Ahmed Ibrahim
793063070b fix: typos in model picker (#6859)
# External (non-OpenAI) Pull Request Requirements

Before opening this Pull Request, please read the dedicated
"Contributing" markdown file or your PR may be closed:
https://github.com/openai/codex/blob/main/docs/contributing.md

If your PR conforms to our contribution guidelines, replace this text
with a detailed and high quality description of your changes.

Include a link to a bug report or enhancement request.
2025-11-19 06:29:02 +00:00
ae
030d1d5b1c chore: update windows docs url (#6877)
- Testing: None
2025-11-19 06:24:17 +00:00
ae
7e6316d4aa feat: tweak windows sandbox strings (#6875)
New strings:
1. Approval mode picker just says "Select Approval Mode"
1. Updated "Auto" to "Agent"
1. When you select "Agent", you get "Agent mode on Windows uses an
experimental sandbox to limit network and filesystem access. [Learn
more]"
1. Updated world-writable warning to "The Windows sandbox cannot protect
writes to folders that are writable by Everyone. Consider removing write
access for Everyone from the following folders: {folders}"

---------

Co-authored-by: iceweasel-oai <iceweasel@openai.com>
2025-11-19 06:00:06 +00:00
Michael Bolin
a75321a64c fix: add more fields to ThreadStartResponse and ThreadResumeResponse (#6847)
This adds the following fields to `ThreadStartResponse` and
`ThreadResumeResponse`:

```rust
    pub model: String,
    pub model_provider: String,
    pub cwd: PathBuf,
    pub approval_policy: AskForApproval,
    pub sandbox: SandboxPolicy,
    pub reasoning_effort: Option<ReasoningEffort>,
```

This is important because these fields are optional in
`ThreadStartParams` and `ThreadResumeParams`, so the caller needs to be
able to determine what values were ultimately used to start/resume the
conversation. (Though note that any of these could be changed later
between turns in the conversation.)

Though to get this information reliably, it must be read from the
internal `SessionConfiguredEvent` that is created in response to the
start of a conversation. Because `SessionConfiguredEvent` (as defined in
`codex-rs/protocol/src/protocol.rs`) did not have all of these fields, a
number of them had to be added as part of this PR.

Because `SessionConfiguredEvent` is referenced in many tests, test
instances of `SessionConfiguredEvent` had to be updated, as well, which
is why this PR touches so many files.
2025-11-18 21:18:43 -08:00
ae
7508e4fd2d chore: update windows sandbox docs (#6872) 2025-11-18 21:02:04 -08:00
pakrym-oai
cac0a6a29d Remote compaction on by-default (#6866) 2025-11-19 02:21:57 +00:00
Celia Chen
b395dc1be6 [app-server] introduce turn/completed v2 event (#6800)
similar to logic in
`codex/codex-rs/exec/src/event_processor_with_jsonl_output.rs`.
translation of v1 -> v2 events:
`codex/event/task_complete` -> `turn/completed`
`codex/event/turn_aborted` -> `turn/completed` with `interrupted` status
`codex/event/error` -> `turn/completed` with `error` status

this PR also makes `items` field in `Turn` optional. For now, we only
populate it when we resume a thread, and leave it as None for all other
places until we properly rewrite core to keep track of items.

tested using the codex app server client. example new event:
```
< {
<   "method": "turn/completed",
<   "params": {
<     "turn": {
<       "id": "0",
<       "items": [],
<       "status": "interrupted"
<     }
<   }
< }
```
2025-11-19 01:55:24 +00:00
zhao-oai
4288091f63 update credit status details (#6862) 2025-11-19 01:40:22 +00:00
Jeremy Rose
526eb3ff82 tui: add branch to 'codex resume', filter by cwd (#6232)
By default, show only sessions that shared a cwd with the current cwd.
`--all` shows all sessions in all cwds. Also, show the branch name from
the rollout metadata.

<img width="1091" height="638" alt="Screenshot 2025-11-04 at 3 30 47 PM"
src="https://github.com/user-attachments/assets/aae90308-6115-455f-aff7-22da5f1d9681"
/>
2025-11-19 00:47:37 +00:00
iceweasel-oai
b952bd2649 smoketest for browser vuln, rough draft of Windows security doc (#6822) 2025-11-18 16:43:34 -08:00
iceweasel-oai
cf57320b9f windows sandbox: support multiple workspace roots (#6854)
The Windows sandbox did not previously support multiple workspace roots
via config. Now it does
2025-11-18 16:35:00 -08:00
zhao-oai
4fb714fb46 updating codex backend models (#6855) 2025-11-18 16:25:50 -08:00
Jeremy Rose
c1391b9f94 exec-server (#6630) 2025-11-19 00:20:19 +00:00
Eric Traut
9275e93364 Fix tests so they don't emit an extraneous config.toml in the source tree (#6853)
This PR fixes the `release_event_does_not_change_selection` test so it
doesn't cause an extra `config.toml` to be emitted in the sources when
running the tests locally. Prior to this fix, I needed to delete this
file every time I ran the tests to prevent it from showing up as an
uncommitted source file.
2025-11-18 15:27:45 -08:00
Owen Lin
b3a824ae3c [app-server-test-client] feat: auto approve command (#6852) 2025-11-18 15:25:02 -08:00
Eric Traut
ab30453dee Improved runtime of generated_ts_has_no_optional_nullable_fields test (#6851)
The `generated_ts_has_no_optional_nullable_fields` test was occasionally
failing on slow CI nodes because of a timeout. This change reduces the
work done by the test. It adds some "options" for the `generate_ts`
function so it can skip work that's not needed for the test.
2025-11-18 15:24:32 -08:00
jif-oai
c56d0c159b fix: local compaction (#6844) 2025-11-18 22:18:10 +00:00
simister
0bf857bc91 Fix typo in config.md for MCP server (#6845)
# External (non-OpenAI) Pull Request Requirements

Before opening this Pull Request, please read the dedicated
"Contributing" markdown file or your PR may be closed:
https://github.com/openai/codex/blob/main/docs/contributing.md

If your PR conforms to our contribution guidelines, replace this text
with a detailed and high quality description of your changes.

Include a link to a bug report or enhancement request.
2025-11-18 14:06:13 -08:00
Anton Panasenko
f7a921039c [codex][otel] support mtls configuration (#6228)
fix for https://github.com/openai/codex/issues/6153

supports mTLS configuration and includes TLS features in the library
build to enable secure HTTPS connections with custom root certificates.

grpc:
https://docs.rs/tonic/0.13.1/src/tonic/transport/channel/endpoint.rs.html#63
https:
https://docs.rs/reqwest/0.12.23/src/reqwest/async_impl/client.rs.html#516
2025-11-18 14:01:01 -08:00
jif-oai
8ddae8cde3 feat: review in app server (#6613) 2025-11-18 21:58:54 +00:00
Dylan Hurd
29ca89c414 chore(config) enable shell_command (#6843)
## Summary
Enables shell_command as default for `gpt-5*` and `codex-*` models.

## Testing
- [x] Updated unit tests
2025-11-18 12:46:02 -08:00
iceweasel-oai
4bada5a84d Prompt to turn on windows sandbox when auto mode selected. (#6618)
- stop prompting users to install WSL 
- prompt users to turn on Windows sandbox when auto mode requested.

<img width="1660" height="195" alt="Screenshot 2025-11-17 110612"
src="https://github.com/user-attachments/assets/c67fc239-a227-417e-94bb-599a8ed8f11e"
/>
<img width="1684" height="168" alt="Screenshot 2025-11-17 110637"
src="https://github.com/user-attachments/assets/d18c3370-830d-4971-8746-04757ae2f709"
/>
<img width="1655" height="293" alt="Screenshot 2025-11-17 110719"
src="https://github.com/user-attachments/assets/d21f6ce9-c23e-4842-baf6-8938b77c16db"
/>
2025-11-18 11:38:18 -08:00
Ahmed Ibrahim
3de8790714 Add the utility to truncate by tokens (#6746)
- This PR is to make it on path for truncating by tokens. This path will
be initially used by unified exec and context manager (responsible for
MCP calls mainly).
- We are exposing new config `calls_output_max_tokens`
- Use `tokens` as the main budget unit but truncate based on the model
family by Introducing `TruncationPolicy`.
- Introduce `truncate_text` as a router for truncation based on the
mode.

In next PRs:
- remove truncate_with_line_bytes_budget
- Add the ability to the model to override the token budget.
2025-11-18 11:36:23 -08:00
Alejandro Peña
b035c604b0 Update faq.md section on supported models (#6832)
Update faq.md to recommend usage of GPT-5.1 Codex, the latest Codex
model from OpenAI.
2025-11-18 09:38:45 -08:00
zhao-oai
e9e644a119 fixing localshell tool calls (#6823)
- Local-shell tool responses were always tagged as
`ExecCommandSource::UserShell` because handler would call
`run_exec_like` with `is_user_shell_cmd` set to true.
- Treat `ToolPayload::LocalShell` the same as other model generated
shell tool calls by deleting `is_user_shell_cmd` from `run_exec_like`
(since actual user shell commands follow a separate code path)
2025-11-18 17:28:26 +00:00
jif-oai
f5d9939cda feat: enable parallel tool calls (#6796) 2025-11-18 17:10:14 +00:00
jif-oai
838531d3e4 feat: remote compaction (#6795)
Co-authored-by: pakrym-oai <pakrym@openai.com>
2025-11-18 16:51:16 +00:00
jif-oai
0eb2e6f9ee nit: app server (#6830) 2025-11-18 16:34:13 +00:00
jif-oai
c20df79a38 nit: mark ghost commit as stable (#6833) 2025-11-18 16:05:49 +00:00
jif-oai
fc55fd7a81 feat: git branch tooling (#6831) 2025-11-18 15:26:09 +00:00
Lael
f3d4e210d8 🐛 fix(rmcp-client): refresh OAuth tokens using expires_at (#6574)
## Summary
- persist OAuth credential expiry timestamps and rehydrate `expires_in`
- proactively refresh rmcp OAuth tokens when `expires_at` is near, then
persist

## Testing
- just fmt
- just fix -p codex-rmcp-client
- cargo test -p codex-rmcp-client

Fixes #6572
2025-11-18 02:16:58 -05:00
Dylan Hurd
28ebe1c97a fix(windows) shell_command on windows, minor parsing (#6811)
## Summary
Enables shell_command for windows users, and starts adding some basic
command parsing here, to at least remove powershell prefixes. We'll
follow this up with command parsing but I wanted to land this change
separately with some basic UX.

**NOTE**: This implementation parses bash and powershell on both
platforms. In theory this is possible, since you can use git bash on
windows or powershell on linux. In practice, this may not be worth the
complexity of supporting, so I don't feel strongly about the current
approach vs. platform-specific branching.

## Testing
- [x] Added a bunch of tests 
- [x] Ran on both windows and os x
2025-11-17 22:23:53 -08:00
Dylan Hurd
2b7378ac77 chore(core) Add shell_serialization coverage (#6810)
## Summary
Similar to #6545, this PR updates the shell_serialization test suite to
cover the various `shell` tool invocations we have. Note that this does
not cover unified_exec, which has its own suite of tests. This should
provide some test coverage for when we eventually consolidate
serialization logic.

## Testing
- [x] These are tests
2025-11-17 19:10:56 -08:00
Ahmed Ibrahim
ddcc60a085 Update defaults to gpt-5.1 (#6652)
## Summary
- update documentation, example configs, and automation defaults to
reference gpt-5.1 / gpt-5.1-codex
- bump the CLI and core configuration defaults, model presets, and error
messaging to the new models while keeping the model-family/tool coverage
for legacy slugs
- refresh tests, fixtures, and TUI snapshots so they expect the upgraded
defaults

## Testing
- `cargo test -p codex-core
config::tests::test_precedence_fixture_with_gpt5_profile`


------
[Codex
Task](https://chatgpt.com/codex/tasks/task_i_6916c5b3c2b08321ace04ee38604fc6b)
2025-11-17 17:40:11 -08:00
cassirer-openai
8465f1f2f4 Demote function call payload log to debug to avoid noisy error-level stderr (#6808) 2025-11-18 01:16:11 +00:00
zhao-oai
7ab45487dd execpolicy2 extension (#6627)
- enabling execpolicy2 parser to parse multiple policy files to build a
combined `Policy` (useful if codex detects many `.codexpolicy` files)
- adding functionality to `Policy` to allow evaluation of multiple cmds
at once (useful when we have chained commands)
2025-11-17 16:44:41 -08:00
Owen Lin
cecbd5b021 [app-server] feat: add v2 command execution approval flow (#6758)
This PR adds the API V2 version of the command‑execution approval flow
for the shell tool.

This PR wires the new RPC (`item/commandExecution/requestApproval`, V2
only) and related events (`item/started`, `item/completed`, and
`item/commandExecution/delta`, which are emitted in both V1 and V2)
through the app-server
protocol. The new approval RPC is only sent when the user initiates a
turn with the new `turn/start` API so we don't break backwards
compatibility with VSCE.

The approach I took was to make as few changes to the Codex core as
possible, leveraging existing `EventMsg` core events, and translating
those in app-server. I did have to add additional fields to
`EventMsg::ExecCommandEndEvent` to capture the command's input so that
app-server can statelessly transform these events to a
`ThreadItem::CommandExecution` item for the `item/completed` event.

Once we stabilize the API and it's complete enough for our partners, we
can work on migrating the core to be aware of command execution items as
a first-class concept.

**Note**: We'll need followup work to make sure these APIs work for the
unified exec tool, but will wait til that's stable and landed before
doing a pass on app-server.

Example payloads below:
```
{
  "method": "item/started",
  "params": {
    "item": {
      "aggregatedOutput": null,
      "command": "/bin/zsh -lc 'touch /tmp/should-trigger-approval'",
      "cwd": "/Users/owen/repos/codex/codex-rs",
      "durationMs": null,
      "exitCode": null,
      "id": "call_lNWWsbXl1e47qNaYjFRs0dyU",
      "parsedCmd": [
        {
          "cmd": "touch /tmp/should-trigger-approval",
          "type": "unknown"
        }
      ],
      "status": "inProgress",
      "type": "commandExecution"
    }
  }
}
```

```
{
  "id": 0,
  "method": "item/commandExecution/requestApproval",
  "params": {
    "itemId": "call_lNWWsbXl1e47qNaYjFRs0dyU",
    "parsedCmd": [
      {
        "cmd": "touch /tmp/should-trigger-approval",
        "type": "unknown"
      }
    ],
    "reason": "Need to create file in /tmp which is outside workspace sandbox",
    "risk": null,
    "threadId": "019a93e8-0a52-7fe3-9808-b6bc40c0989a",
    "turnId": "1"
  }
}
```

```
{
  "id": 0,
  "result": {
    "acceptSettings": {
      "forSession": false
    },
    "decision": "accept"
  }
}
```

```
{
  "params": {
    "item": {
      "aggregatedOutput": null,
      "command": "/bin/zsh -lc 'touch /tmp/should-trigger-approval'",
      "cwd": "/Users/owen/repos/codex/codex-rs",
      "durationMs": 224,
      "exitCode": 0,
      "id": "call_lNWWsbXl1e47qNaYjFRs0dyU",
      "parsedCmd": [
        {
          "cmd": "touch /tmp/should-trigger-approval",
          "type": "unknown"
        }
      ],
      "status": "completed",
      "type": "commandExecution"
    }
  }
}
```
2025-11-18 00:23:54 +00:00
zhao-oai
4000e26303 background rate limits fetch (#6789)
fetching rate limits every minute asynchronously
2025-11-17 16:06:26 -08:00
iceweasel-oai
e032d338f2 move cap_sid file into ~/.codex so the sandbox cannot overwrite it (#6798)
The `cap_sid` file contains the IDs of the two custom SIDs that the
Windows sandbox creates/manages to implement read-only and
workspace-write sandbox policies.

It previously lived in `<cwd>/.codex` which means that the sandbox could
write to it, which could degrade the efficacy of the sandbox. This
change moves it to `~/.codex/` (or wherever `CODEX_HOME` points to) so
that it is outside the workspace.
2025-11-17 15:49:41 -08:00
Eric Traut
8bebe86a47 Fix TUI issues with Alt-Gr on Windows (#6799)
This PR fixes keyboard handling for the Right Alt (aka "Alt-Gr") key on
Windows. This key appears on keyboards in Central and Eastern Europe.
Codex has effectively never worked for Windows users in these regions
because the code didn't properly handle this key, which is used for
typing common symbols like `\` and `@`.

A few days ago, I merged a [community-authored
PR](https://github.com/openai/codex/pull/6720) that supplied a partial
fix for this issue. Upon closer inspect, that PR was 1) too broad (not
scoped to Windows only) and 2) incomplete (didn't fix all relevant code
paths, so paste was still broken).

This improvement is based on another [community-provided
PR](https://github.com/openai/codex/pull/3241) by @marektomas-cz. He
submitted it back in September and later closed it because it didn't
receive any attention.

This fix addresses the following bugs: #5922, #3046, #3092, #3519,
#5684, #5843.
2025-11-17 15:18:16 -08:00
Jeremy Rose
ab2e7499f8 core: add a feature to disable the shell tool (#6481)
`--disable shell_tool` disables the built-in shell tool. This is useful
for MCP-only operation.

---------

Co-authored-by: Michael Bolin <mbolin@openai.com>
2025-11-17 22:56:19 +00:00
Dylan Hurd
daf77b8452 chore(core) Update shell instructions (#6679)
## Summary
Consolidates `shell` and `shell_command` tool instructions.
## Testing 
- [x] Updated tests, tested locally
2025-11-17 13:05:15 -08:00
Owen Lin
03a6e853c0 fix: annotate all app server v2 types with camelCase (#6791) 2025-11-17 12:02:52 -08:00
rugvedS07
837bc98a1d LM Studio OSS Support (#2312)
## Overview

Adds LM Studio OSS support. Closes #1883


### Changes
This PR enhances the behavior of `--oss` flag to support LM Studio as a
provider. Additionally, it introduces a new flag`--local-provider` which
can take in `lmstudio` or `ollama` as values if the user wants to
explicitly choose which one to use.

If no provider is specified `codex --oss` will auto-select the provider
based on whichever is running.

#### Additional enhancements 
The default can be set using `oss-provider` in config like:

```
oss_provider = "lmstudio"
```

For non-interactive users, they will need to either provide the provider
as an arg or have it in their `config.toml`

### Notes
For best performance, [set the default context
length](https://lmstudio.ai/docs/app/advanced/per-model) for gpt-oss to
the maximum your machine can support

---------

Co-authored-by: Matt Clayton <matt@lmstudio.ai>
Co-authored-by: Eric Traut <etraut@openai.com>
2025-11-17 11:49:09 -08:00
Celia Chen
842a1b7fe7 [app-server] add events to readme (#6690)
add table of contents, lifecycle and events to readme.
2025-11-17 19:28:05 +00:00
Jeremy Rose
03ffe4d595 core/tui: non-blocking MCP startup (#6334)
This makes MCP startup not block TUI startup. Messages sent while MCPs
are booting will be queued.


https://github.com/user-attachments/assets/96e1d234-5d8f-4932-a935-a675d35c05e0


Fixes #6317

---------

Co-authored-by: pakrym-oai <pakrym@openai.com>
2025-11-17 11:26:11 -08:00
Owen Lin
ae2a084fae chore: delete chatwidget::tests::binary_size_transcript_snapshot tui test (#6759)
We're running into quite a bit of drag maintaining this test, since
every time we add fields to an EventMsg that happened to be dumped into
the `binary-size-log.jsonl` fixture, this test starts to fail. The fix
is usually to either manually update the `binary-size-log.jsonl` fixture
file, or update the `upgrade_event_payload_for_tests` function to map
the data in that file into something workable.

Eason says it's fine to delete this test, so let's just delete it
2025-11-17 11:11:41 -08:00
zhao-oai
a941ae7632 feat: execpolicy v2 (#6467)
## Summary
- Introduces the `codex-execpolicy2` crate.
- This PR covers only the prefix-rule subset of the planned execpolicy
v2 language; a richer language will follow.

## Policy
- Policy language centers on `prefix_rule(pattern=[...], decision?,
match?, not_match?)`, where `pattern` is an ordered list of tokens; any
element may be a list to denote alternatives. `decision` defaults to
`allow`; valid values are `allow`, `prompt`, and `forbidden`. `match` /
`not_match` hold example commands that are tokenized and validated at
load time (think of these as unit tests).

## Policy shapes
- Prefix rules use Starlark syntax:
```starlark
prefix_rule(
    pattern = ["cmd", ["alt1", "alt2"]], # ordered tokens; list entries denote alternatives
    decision = "prompt",                # allow | prompt | forbidden; defaults to allow
    match = [["cmd", "alt1"]],          # examples that must match this rule (enforced at compile time)
    not_match = [["cmd", "oops"]],      # examples that must not match this rule (enforced at compile time)
)
```

## Response shapes
- Match:

```json
{
  "match": {
    "decision": "allow|prompt|forbidden",
    "matchedRules": [
      {
        "prefixRuleMatch": {
          "matchedPrefix": ["<token>", "..."],
          "decision": "allow|prompt|forbidden"
        }
      }
    ]
  }
}
```

- No match:

```json
"noMatch"
```

- `matchedRules` lists every rule whose prefix matched the command;
`matchedPrefix` is the exact prefix that matched.
- The effective `decision` is the strictest severity across all matches
(`forbidden` > `prompt` > `allow`).

---------

Co-authored-by: Michael Bolin <mbolin@openai.com>
2025-11-17 10:15:45 -08:00
jif-oai
2c665fb1dd nit: personal git ignore (#6787) 2025-11-17 17:45:52 +00:00
jif-oai
98a90a3bb2 tmp: drop sccache for windows 2 (#6775) 2025-11-17 16:39:15 +00:00
jif-oai
7c8d333980 feat: placeholder for image that can't be decoded to prevent 400 (#6773) 2025-11-17 16:10:53 +00:00
Dylan Hurd
497fb4a19c fix(core) serialize shell_command (#6744)
## Summary
Ensures we're serializing calls to `shell_command`

## Testing
- [x] Added unit test
2025-11-16 23:16:51 -08:00
Xiao-Yong Jin
5860481bc4 Fix FreeBSD/OpenBSD builds: target-specific keyring features and BSD hardening (#6680)
## Summary
Builds on FreeBSD and OpenBSD were failing due to globally enabled
Linux-specific keyring features and hardening code paths not gated by
OS. This PR scopes keyring native backends to the
appropriate targets, disables default features at the workspace root,
and adds a BSD-specific hardening function. Linux/macOS/Windows behavior
remains unchanged, while FreeBSD/OpenBSD
  now build and run with a supported backend.

## Key Changes

  - Keyring features:
- Disable keyring default features at the workspace root to avoid
pulling Linux backends on non-Linux.
- Move native backend features into target-specific sections in the
affected crates:
          - Linux: linux-native-async-persistent
          - macOS: apple-native
          - Windows: windows-native
          - FreeBSD/OpenBSD: sync-secret-service
  - Process hardening:
      - Add pre_main_hardening_bsd() for FreeBSD/OpenBSD, applying:
          - Set RLIMIT_CORE to 0
          - Clear LD_* environment variables
- Simplify process-hardening Cargo deps to unconditional libc (avoid
conflicting OS fragments).
  - No changes to CODEX_SANDBOX_* behavior.

## Rationale

- Previously, enabling keyring native backends globally pulled
Linux-only features on BSD, causing build errors.
- Hardening logic was tailored for Linux/macOS; BSD builds lacked a
gated path with equivalent safeguards.
- Target-scoped features and BSD hardening make the crates portable
across these OSes without affecting existing behavior elsewhere.

## Impact by Platform

  - Linux: No functional change; backends now selected via target cfg.
  - macOS: No functional change; explicit apple-native mapping.
  - Windows: No functional change; explicit windows-native mapping.
- FreeBSD/OpenBSD: Builds succeed using sync-secret-service; BSD
hardening applied during startup.

## Testing

- Verified compilation across affected crates with target-specific
features.
- Smoke-checked that Linux/macOS/Windows feature sets remain identical
functionally after scoping.
- On BSD, confirmed keyring resolves to sync-secret-service and
hardening compiles.

## Risks / Compatibility

  - Minimal risk: only feature scoping and OS-gated additions.
- No public API changes in the crates; runtime behavior on non-BSD
platforms is preserved.
- On BSD, the new hardening clears LD_*; this is consistent with
security posture on other Unix platforms.

## Reviewer Notes

- Pay attention to target-specific sections for keyring in the affected
Cargo.toml files.
- Confirm pre_main_hardening_bsd() mirrors the safe subset of
Linux/macOS hardening without introducing Linux-only calls.
- Confirm no references to CODEX_SANDBOX_ENV_VAR or
CODEX_SANDBOX_NETWORK_DISABLED_ENV_VAR were added/modified.

## Checklist

  - Disable keyring default features at workspace root.
- Target-specific keyring features mapped per OS
(Linux/macOS/Windows/BSD).
  - Add BSD hardening (RLIMIT_CORE=0, clear LD_*).
  - Simplify process-hardening dependencies to unconditional libc.
  - No changes to sandbox env var code.
  - Formatting and linting: just fmt + just fix -p for changed crates.
  - Project tests pass for changed crates; broader suite unchanged.

---------

Co-authored-by: celia-oai <celia@openai.com>
2025-11-17 05:07:34 +00:00
Eric Traut
a52cf4d2b4 Exempt the "codex" github user from signing the CLA (#6724)
This fixes bug #6697
2025-11-16 20:49:31 -08:00
dependabot[bot]
e70c52a3af chore(deps): bump actions/github-script from 7 to 8 (#6755)
Bumps [actions/github-script](https://github.com/actions/github-script)
from 7 to 8.
<details>
<summary>Release notes</summary>
<p><em>Sourced from <a
href="https://github.com/actions/github-script/releases">actions/github-script's
releases</a>.</em></p>
<blockquote>
<h2>v8.0.0</h2>
<h2>What's Changed</h2>
<ul>
<li>Update Node.js version support to 24.x by <a
href="https://github.com/salmanmkc"><code>@​salmanmkc</code></a> in <a
href="https://redirect.github.com/actions/github-script/pull/637">actions/github-script#637</a></li>
<li>README for updating actions/github-script from v7 to v8 by <a
href="https://github.com/sneha-krip"><code>@​sneha-krip</code></a> in <a
href="https://redirect.github.com/actions/github-script/pull/653">actions/github-script#653</a></li>
</ul>
<h2>⚠️ Minimum Compatible Runner Version</h2>
<p><strong>v2.327.1</strong><br />
<a
href="https://github.com/actions/runner/releases/tag/v2.327.1">Release
Notes</a></p>
<p>Make sure your runner is updated to this version or newer to use this
release.</p>
<h2>New Contributors</h2>
<ul>
<li><a href="https://github.com/salmanmkc"><code>@​salmanmkc</code></a>
made their first contribution in <a
href="https://redirect.github.com/actions/github-script/pull/637">actions/github-script#637</a></li>
<li><a
href="https://github.com/sneha-krip"><code>@​sneha-krip</code></a> made
their first contribution in <a
href="https://redirect.github.com/actions/github-script/pull/653">actions/github-script#653</a></li>
</ul>
<p><strong>Full Changelog</strong>: <a
href="https://github.com/actions/github-script/compare/v7.1.0...v8.0.0">https://github.com/actions/github-script/compare/v7.1.0...v8.0.0</a></p>
<h2>v7.1.0</h2>
<h2>What's Changed</h2>
<ul>
<li>Upgrade husky to v9 by <a
href="https://github.com/benelan"><code>@​benelan</code></a> in <a
href="https://redirect.github.com/actions/github-script/pull/482">actions/github-script#482</a></li>
<li>Add workflow file for publishing releases to immutable action
package by <a
href="https://github.com/Jcambass"><code>@​Jcambass</code></a> in <a
href="https://redirect.github.com/actions/github-script/pull/485">actions/github-script#485</a></li>
<li>Upgrade IA Publish by <a
href="https://github.com/Jcambass"><code>@​Jcambass</code></a> in <a
href="https://redirect.github.com/actions/github-script/pull/486">actions/github-script#486</a></li>
<li>Fix workflow status badges by <a
href="https://github.com/joshmgross"><code>@​joshmgross</code></a> in <a
href="https://redirect.github.com/actions/github-script/pull/497">actions/github-script#497</a></li>
<li>Update usage of <code>actions/upload-artifact</code> by <a
href="https://github.com/joshmgross"><code>@​joshmgross</code></a> in <a
href="https://redirect.github.com/actions/github-script/pull/512">actions/github-script#512</a></li>
<li>Clear up package name confusion by <a
href="https://github.com/joshmgross"><code>@​joshmgross</code></a> in <a
href="https://redirect.github.com/actions/github-script/pull/514">actions/github-script#514</a></li>
<li>Update dependencies with <code>npm audit fix</code> by <a
href="https://github.com/joshmgross"><code>@​joshmgross</code></a> in <a
href="https://redirect.github.com/actions/github-script/pull/515">actions/github-script#515</a></li>
<li>Specify that the used script is JavaScript by <a
href="https://github.com/timotk"><code>@​timotk</code></a> in <a
href="https://redirect.github.com/actions/github-script/pull/478">actions/github-script#478</a></li>
<li>chore: Add Dependabot for NPM and Actions by <a
href="https://github.com/nschonni"><code>@​nschonni</code></a> in <a
href="https://redirect.github.com/actions/github-script/pull/472">actions/github-script#472</a></li>
<li>Define <code>permissions</code> in workflows and update actions by
<a href="https://github.com/joshmgross"><code>@​joshmgross</code></a> in
<a
href="https://redirect.github.com/actions/github-script/pull/531">actions/github-script#531</a></li>
<li>chore: Add Dependabot for .github/actions/install-dependencies by <a
href="https://github.com/nschonni"><code>@​nschonni</code></a> in <a
href="https://redirect.github.com/actions/github-script/pull/532">actions/github-script#532</a></li>
<li>chore: Remove .vscode settings by <a
href="https://github.com/nschonni"><code>@​nschonni</code></a> in <a
href="https://redirect.github.com/actions/github-script/pull/533">actions/github-script#533</a></li>
<li>ci: Use github/setup-licensed by <a
href="https://github.com/nschonni"><code>@​nschonni</code></a> in <a
href="https://redirect.github.com/actions/github-script/pull/473">actions/github-script#473</a></li>
<li>make octokit instance available as octokit on top of github, to make
it easier to seamlessly copy examples from GitHub rest api or octokit
documentations by <a
href="https://github.com/iamstarkov"><code>@​iamstarkov</code></a> in <a
href="https://redirect.github.com/actions/github-script/pull/508">actions/github-script#508</a></li>
<li>Remove <code>octokit</code> README updates for v7 by <a
href="https://github.com/joshmgross"><code>@​joshmgross</code></a> in <a
href="https://redirect.github.com/actions/github-script/pull/557">actions/github-script#557</a></li>
<li>docs: add &quot;exec&quot; usage examples by <a
href="https://github.com/neilime"><code>@​neilime</code></a> in <a
href="https://redirect.github.com/actions/github-script/pull/546">actions/github-script#546</a></li>
<li>Bump ruby/setup-ruby from 1.213.0 to 1.222.0 by <a
href="https://github.com/dependabot"><code>@​dependabot</code></a>[bot]
in <a
href="https://redirect.github.com/actions/github-script/pull/563">actions/github-script#563</a></li>
<li>Bump ruby/setup-ruby from 1.222.0 to 1.229.0 by <a
href="https://github.com/dependabot"><code>@​dependabot</code></a>[bot]
in <a
href="https://redirect.github.com/actions/github-script/pull/575">actions/github-script#575</a></li>
<li>Clearly document passing inputs to the <code>script</code> by <a
href="https://github.com/joshmgross"><code>@​joshmgross</code></a> in <a
href="https://redirect.github.com/actions/github-script/pull/603">actions/github-script#603</a></li>
<li>Update README.md by <a
href="https://github.com/nebuk89"><code>@​nebuk89</code></a> in <a
href="https://redirect.github.com/actions/github-script/pull/610">actions/github-script#610</a></li>
</ul>
<h2>New Contributors</h2>
<ul>
<li><a href="https://github.com/benelan"><code>@​benelan</code></a> made
their first contribution in <a
href="https://redirect.github.com/actions/github-script/pull/482">actions/github-script#482</a></li>
<li><a href="https://github.com/Jcambass"><code>@​Jcambass</code></a>
made their first contribution in <a
href="https://redirect.github.com/actions/github-script/pull/485">actions/github-script#485</a></li>
<li><a href="https://github.com/timotk"><code>@​timotk</code></a> made
their first contribution in <a
href="https://redirect.github.com/actions/github-script/pull/478">actions/github-script#478</a></li>
<li><a
href="https://github.com/iamstarkov"><code>@​iamstarkov</code></a> made
their first contribution in <a
href="https://redirect.github.com/actions/github-script/pull/508">actions/github-script#508</a></li>
<li><a href="https://github.com/neilime"><code>@​neilime</code></a> made
their first contribution in <a
href="https://redirect.github.com/actions/github-script/pull/546">actions/github-script#546</a></li>
<li><a href="https://github.com/nebuk89"><code>@​nebuk89</code></a> made
their first contribution in <a
href="https://redirect.github.com/actions/github-script/pull/610">actions/github-script#610</a></li>
</ul>
<p><strong>Full Changelog</strong>: <a
href="https://github.com/actions/github-script/compare/v7...v7.1.0">https://github.com/actions/github-script/compare/v7...v7.1.0</a></p>
<!-- raw HTML omitted -->
</blockquote>
<p>... (truncated)</p>
</details>
<details>
<summary>Commits</summary>
<ul>
<li><a
href="ed597411d8"><code>ed59741</code></a>
Merge pull request <a
href="https://redirect.github.com/actions/github-script/issues/653">#653</a>
from actions/sneha-krip/readme-for-v8</li>
<li><a
href="2dc352e4ba"><code>2dc352e</code></a>
Bold minimum Actions Runner version in README</li>
<li><a
href="01e118c8d0"><code>01e118c</code></a>
Update README for Node 24 runtime requirements</li>
<li><a
href="8b222ac82e"><code>8b222ac</code></a>
Apply suggestion from <a
href="https://github.com/salmanmkc"><code>@​salmanmkc</code></a></li>
<li><a
href="adc0eeac99"><code>adc0eea</code></a>
README for updating actions/github-script from v7 to v8</li>
<li><a
href="20fe497b3f"><code>20fe497</code></a>
Merge pull request <a
href="https://redirect.github.com/actions/github-script/issues/637">#637</a>
from actions/node24</li>
<li><a
href="e7b7f222b1"><code>e7b7f22</code></a>
update licenses</li>
<li><a
href="2c81ba05f3"><code>2c81ba0</code></a>
Update Node.js version support to 24.x</li>
<li>See full diff in <a
href="https://github.com/actions/github-script/compare/v7...v8">compare
view</a></li>
</ul>
</details>
<br />


[![Dependabot compatibility
score](https://dependabot-badges.githubapp.com/badges/compatibility_score?dependency-name=actions/github-script&package-manager=github_actions&previous-version=7&new-version=8)](https://docs.github.com/en/github/managing-security-vulnerabilities/about-dependabot-security-updates#about-compatibility-scores)

Dependabot will resolve any conflicts with this PR as long as you don't
alter it yourself. You can also trigger a rebase manually by commenting
`@dependabot rebase`.

[//]: # (dependabot-automerge-start)
[//]: # (dependabot-automerge-end)

---

<details>
<summary>Dependabot commands and options</summary>
<br />

You can trigger Dependabot actions by commenting on this PR:
- `@dependabot rebase` will rebase this PR
- `@dependabot recreate` will recreate this PR, overwriting any edits
that have been made to it
- `@dependabot merge` will merge this PR after your CI passes on it
- `@dependabot squash and merge` will squash and merge this PR after
your CI passes on it
- `@dependabot cancel merge` will cancel a previously requested merge
and block automerging
- `@dependabot reopen` will reopen this PR if it is closed
- `@dependabot close` will close this PR and stop Dependabot recreating
it. You can achieve the same result by closing it manually
- `@dependabot show <dependency name> ignore conditions` will show all
of the ignore conditions of the specified dependency
- `@dependabot ignore this major version` will close this PR and stop
Dependabot creating any more for this major version (unless you reopen
the PR or upgrade to it yourself)
- `@dependabot ignore this minor version` will close this PR and stop
Dependabot creating any more for this minor version (unless you reopen
the PR or upgrade to it yourself)
- `@dependabot ignore this dependency` will close this PR and stop
Dependabot creating any more for this dependency (unless you reopen the
PR or upgrade to it yourself)


</details>

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-11-16 19:53:19 -08:00
dulikaifazr
de1768d3ba Fix: Claude models return incomplete responses due to empty finish_reason handling (#6728)
## Summary
Fixes streaming issue where Claude models return only 1-4 characters
instead of full responses when used through certain API
providers/proxies.

## Environment
- **OS**: Windows
- **Models affected**: Claude models (e.g., claude-haiku-4-5-20251001)
- **API Provider**: AAAI API proxy (https://api.aaai.vip/v1)
- **Working models**: GLM, Google models work correctly

## Problem
When using Claude models in both TUI and exec modes, only 1-4 characters
are displayed despite the backend receiving the full response. Debug
logs revealed that some API providers send SSE chunks with an empty
string finish_reason during active streaming, rather than null or
omitting the field entirely.

The current code treats any non-null finish_reason as a termination
signal, causing the stream to exit prematurely after the first chunk.
The problematic chunks contain finish_reason with an empty string
instead of null.

## Solution
Fix empty finish_reason handling in chat_completions.rs by adding a
check to only process non-empty finish_reason values. This ensures empty
strings are ignored and streaming continues normally.

## Testing
- Tested on Windows with Claude Haiku model via AAAI API proxy
- Full responses now received and displayed correctly in both TUI and
exec modes
- Other models (GLM, Google) continue to work as expected
- No regression in existing functionality

## Impact
- Improves compatibility with API providers that send empty
finish_reason during streaming
- Enables Claude models to work correctly in Windows environment
- No breaking changes to existing functionality

## Related Issues
This fix resolves the issue where Claude models appeared to return
incomplete responses. The root cause was identified as a compatibility
issue in parsing SSE responses from certain API providers/proxies,
rather than a model-specific problem. This change improves overall
robustness when working with various API endpoints.

---------

Co-authored-by: Eric Traut <etraut@openai.com>
2025-11-16 19:50:36 -08:00
Akrelion45
702238f004 Fix AltGr/backslash input on Windows Codex terminal (#6720)
### Summary

- Treat AltGr chords (Ctrl+Alt) as literal character input in the Codex
TUI textarea so Windows terminals that report
    backslash and other characters via AltGr insert correctly.
- Add regression test altgr_ctrl_alt_char_inserts_literal to ensure
Ctrl+Alt char events append the character and
    advance the cursor.

 ### Motivation

On US/UK keyboard layouts, backslash is produced by a plain key, so
Ctrl+Alt handling is never exercised and the
bug isn’t visible. On many non‑US layouts (e.g., German), backslash and
other symbols require AltGr, which terminals
report as Ctrl+Alt+<char>. Our textarea previously filtered these chords
like navigation bindings, so AltGr input was
dropped on affected layouts. This change treats AltGr chords as literal
input so backslash and similar symbols work on
  Windows terminals.

This fixes multiple reported Issues where the \ symbol got cut off.
Like:
C:\Users\Admin
became
C:UsersAdmin

Co-authored-by: Eric Traut <etraut@openai.com>
2025-11-16 19:15:06 -08:00
Eric Traut
fa5f6e76c9 Revert "tmp: drop sccache for windows (#6673)" (#6751)
This reverts commit 4719cba19a
2025-11-16 18:37:12 -08:00
Joonsoo Lee
f828cd2897 fix: resolve Windows MCP server execution for script-based tools (#3828)
## What?

Fixes MCP server initialization failures on Windows when using
script-based tools like `npx`, `pnpm`, and `yarn` that rely on
`.cmd`/`.bat` files rather than `.exe` binaries.

Fixes #2945

## Why?

Windows users encounter "program not found" errors when configuring MCP
servers with commands like `npx` in their `~/.codex/config.toml`. This
happens because:

- Tools like `npx` are batch scripts (`npx.cmd`) on Windows, not
executable binaries
- Rust's `std::process::Command` bypasses the shell and cannot execute
these scripts directly
- The Windows shell normally handles this by checking `PATHEXT` for
executable extensions

Without this fix, Windows users must specify full paths or add `.cmd`
extensions manually, which breaks cross-platform compatibility.

## How?

Added platform-specific program resolution using the `which` crate to
find the correct executable path:

- **Windows**: Resolves programs through PATH/PATHEXT to find
`.cmd`/`.bat` scripts
- **Unix**: Returns the program unchanged (no-op, as Unix handles
scripts natively)

### Changes

- Added `which = "6"` dependency to `mcp-client/Cargo.toml`
- Implemented `program_resolver` module in `mcp_client.rs` with
platform-specific resolution
- Added comprehensive tests for both Windows and Unix behavior

### Testing

Added platform-specific tests to verify:
- Unix systems execute scripts without extensions
- Windows fails without proper extensions
- Windows succeeds with explicit extensions
- Cross-platform resolution enables successful execution

**Tested on:**
- Windows 11 (NT 10.0.26100.0 x64)
- PowerShell 5.1 & 7+, CMD, Git Bash
- MCP servers: playwright, context7, supabase
- WSL (verified no regression)

**Local checks passed:**
```bash
cargo test && cargo clippy --tests && cargo fmt -- --config imports_granularity=Item
```

### Results

**Before:**
```
🖐 MCP client for `playwright` failed to start: program not found
```

**After:**
```
🖐 MCP client for `playwright` failed to start: request timed out
```

Windows users can now use simple commands like `npx` in their config
without specifying full paths or extensions. The timeout issue is a
separate concern that will be addressed in a follow-up PR.

---------

Co-authored-by: Eric Traut <etraut@openai.com>
2025-11-16 13:41:10 -08:00
Abkari Mohammed Sayeem
326c1e0a7e Fix documentation errors for Custom Prompts named arguments and add canonical examples (#5910)
The Custom Prompts documentation (docs/prompts.md) was incomplete for
named arguments:

1. **Documentation for custom prompts was incomplete** - named argument
usage was mentioned briefly but lacked comprehensive canonical examples
showing proper syntax and behavior.

2. **Fixed by adding canonical, tested syntax and examples:**
   - Example 1: Basic named arguments with TICKET_ID and TICKET_TITLE
   - Example 2: Mixed positional and named arguments with FILE and FOCUS
   - Example 3: Using positional arguments
- Example 4: Updated draftpr example to use proper $FEATURE_NAME syntax
   - Added clear usage examples showing KEY=value syntax
   - Added expanded prompt examples showing the result
   - Documented error handling and validation requirements

3. **Added Implementation Reference section** that references the
relevant feature implementation from the codebase (PRs #4470 and #4474
for initial implementation, #5332 and #5403 for clarifications).

This addresses issue #5039 by providing complete, accurate documentation
for named argument usage in custom prompts.

---------

Co-authored-by: Eric Traut <etraut@openai.com>
2025-11-15 09:25:46 -08:00
Ahmed Ibrahim
3f1c4b9add Tighten panic on double truncation (#6701) 2025-11-15 07:28:59 +00:00
Ahmed Ibrahim
0b28e72b66 Improve compact (#6692)
This PR does the following:
- Add compact prefix to the summary
- Change the compaction prompt
- Allow multiple compaction for long running tasks
- Filter out summary messages on the following compaction

Considerations:
- Filtering out the summary message isn't the most clean
- Theoretically, we can end up in infinite compaction loop if the user
messages > compaction limit . However, that's not possible in today's
code because we have hard cap on user messages.
- We need to address having multiple user messages because it confuses
the model.

Testing:
- Making sure that after compact we always end up with one user message
(task) and one summary, even on multiple compaction.
2025-11-15 07:17:51 +00:00
Ahmed Ibrahim
94dfb211af Refactor truncation helpers into its own file (#6683)
That's to centralize the truncation in one place. Next step would be to
make only two methods public: one with bytes/lines and one with tokens.
2025-11-15 06:44:23 +00:00
Ahmed Ibrahim
b560c5cef1 Revert "templates and build step for validating/submitting winget package" (#6696)
Reverts openai/codex#6485
2025-11-15 03:47:58 +00:00
Josh McKinney
4ae986967c ci: only run CLA assistant for openai org repos (#6687)
This prevents notifications coming from PRs on forked repos
2025-11-14 17:34:14 -08:00
Vinicius da Motta
89ecc00b79 Handle "Don't Trust" directory selection in onboarding (#4941)
Fixes #4940
Fixes #4892

When selecting "No, ask me to approve edits and commands" during
onboarding, the code wasn't applying the correct approval policy,
causing Codex to block all write operations instead of requesting
approval.

This PR fixes the issue by persisting the "DontTrust" decision in
config.toml as `trust_level = "untrusted"` and handling it in the
sandbox and approval policy logic, so Codex correctly asks for approval
before making changes.

## Before (bug)
<img width="709" height="500" alt="bef"
src="https://github.com/user-attachments/assets/5aced26d-d810-4754-879a-89d9e4e0073b"
/>

## After (fixed)
<img width="713" height="359" alt="aft"
src="https://github.com/user-attachments/assets/9887bbcb-a9a5-4e54-8e76-9125a782226b"
/>

---------

Co-authored-by: Eric Traut <etraut@openai.com>
2025-11-14 15:23:35 -08:00
pakrym-oai
018a2d2e50 Ignore unified_exec_respects_workdir_override (#6693) 2025-11-14 15:00:31 -08:00
pakrym-oai
cfcc87a953 Order outputs before inputs (#6691)
For better caching performance all output items should be rendered in
the order they were produced before all new input items (for example,
all function_call before all function_call_output).
2025-11-14 14:54:11 -08:00
Owen Lin
c3951e505d feat: add app-server-test-client crate for internal use (#5391)
For app-server development it's been helpful to be able to trigger some
test flows end-to-end and print the JSON-RPC messages sent between
client and server.
2025-11-14 12:39:58 -08:00
iceweasel-oai
abb7b79701 fix codex detection, add new security-focused smoketests. (#6682)
Fix 'codex' detection to look for debug build, then release build, then
installed.

Adds more smoketests around security from @viyatb-oai
2025-11-14 12:08:59 -08:00
Ryan Lopopolo
936650001f feat(ts-sdk): allow overriding CLI environment (#6648)
## Summary
- add an `env` option for the TypeScript Codex client and plumb it into
`CodexExec` so the CLI can run without inheriting `process.env`
- extend the test spy to capture spawn environments, add coverage for
the new option, and document how to use it

## Testing
- `pnpm test` *(fails: corepack cannot download pnpm because outbound
network access is blocked in the sandbox)*

------
[Codex
Task](https://chatgpt.com/codex/tasks/task_i_6916b2d7c7548322a72d61d91a2dac85)
2025-11-14 19:44:19 +00:00
iceweasel-oai
37fba28ac3 templates and build step for validating/submitting winget package (#6485) 2025-11-14 11:06:44 -08:00
pakrym-oai
4ba562d2dd Add test timeout (#6612)
Add an overall test timeout of 30s.
2025-11-14 09:30:37 -08:00
Jeremy Rose
799364de87 Enable TUI notifications by default (#6633)
## Summary
- default the `tui.notifications` setting to enabled so desktop
notifications work out of the box
- update configuration tests and documentation to reflect the new
default

## Testing
- `cargo test -p codex-core` *(fails:
`exec::tests::kill_child_process_group_kills_grandchildren_on_timeout`
is flaky in this sandbox because the spawned grandchild process stays
alive)*
- `cargo test -p codex-core
exec::tests::kill_child_process_group_kills_grandchildren_on_timeout`
*(fails: same sandbox limitation as above)*

------
[Codex
Task](https://chatgpt.com/codex/tasks/task_i_69166f811144832c9e8aaf8ee2642373)
2025-11-14 09:28:09 -08:00
jif-oai
4719cba19a tmp: drop sccache for windows (#6673) 2025-11-14 17:29:05 +01:00
Celia Chen
526777c9b4 [App server] add mcp tool call item started/completed events (#6642)
this PR does two things:
1. refactor `apply_bespoke_event_handling` into a separate file as it's
getting kind of long;
2. add mcp tool call `item/started` and `item/completed` events. To roll
out app server events asap we didn't properly migrate mcp core events to
use TurnItem for mcp tool calls - this will be a follow-up PR.

real events generated in log:
```
{
  "method": "codex/event/mcp_tool_call_end",
  "params": {
    "conversationId": "019a8021-26af-7c20-83db-21ca81e44d68",
    "id": "0",
    "msg": {
      "call_id": "call_7EjRQkD9HnfyMWf7tGrT9FKA",
      "duration": {
        "nanos": 92708,
        "secs": 0
      },
      "invocation": {
        "arguments": {
          "server": ""
        },
        "server": "codex",
        "tool": "list_mcp_resources"
      },
      "result": {
        "Ok": {
          "content": [
            {
              "text": "{\"resources\":[]}",
              "type": "text"
            }
          ],
          "isError": false
        }
      },
      "type": "mcp_tool_call_end"
    }
  }
}

{
  "method": "item/completed",
  "params": {
    "item": {
      "arguments": {
        "server": ""
      },
      "error": null,
      "id": "call_7EjRQkD9HnfyMWf7tGrT9FKA",
      "result": {
        "content": [
          {
            "text": "{\"resources\":[]}",
            "type": "text"
          }
        ],
        "structuredContent": null
      },
      "server": "codex",
      "status": "completed",
      "tool": "list_mcp_resources",
      "type": "mcpToolCall"
    }
  }
}
```
2025-11-14 08:08:43 -08:00
jif-oai
f17b392470 feat: cache tokenizer (#6609) 2025-11-14 17:05:00 +01:00
jif-oai
63c8c01f40 feat: better UI for unified_exec (#6515)
<img width="376" height="132" alt="Screenshot 2025-11-12 at 17 36 22"
src="https://github.com/user-attachments/assets/ce693f0d-5ca0-462e-b170-c20811dcc8d5"
/>
2025-11-14 16:31:12 +01:00
jif-oai
4788fb179a feat: add resume logs when doing /new (#6660)
<img width="769" height="803" alt="Screenshot 2025-11-14 at 10 25 49"
src="https://github.com/user-attachments/assets/12fbc21e-cab9-4d0a-a484-1aeb60219f96"
/>
2025-11-14 11:42:16 +01:00
pakrym-oai
6c384eb9c6 tests: replace mount_sse_once_match with mount_sse_once for SSE mocking (#6640) 2025-11-13 18:04:05 -08:00
Ahmed Ibrahim
2a6e9b20df Promote shared helpers for suite tests (#6460)
## Summary
- add `TestCodex::submit_turn_with_policies` and extend the response
helpers with reusable tool-call utilities
- update the grep_files, read_file, list_dir, shell_serialization, and
tools suites to rely on the shared helpers instead of local copies
- make the list_dir helper return `anyhow::Result` so clippy no longer
warns about `expect`

## Testing
- `just fix -p codex-core`
- `cargo test -p codex-core --test all
suite::grep_files::grep_files_tool_collects_matches`
- `cargo test -p codex-core
suite::grep_files::grep_files_tool_collects_matches -- --ignored`
(filter requests ignored tests so nothing runs, but the build stays
clean)


------
[Codex
Task](https://chatgpt.com/codex/tasks/task_i_69112d53abac83219813cab4d7cb6446)
2025-11-13 17:12:10 -08:00
Ahmed Ibrahim
f3c6b1334b Use shared network gating helper in chat completion tests (#6461)
## Summary
- replace the bespoke network check in the chat completion payload and
SSE tests with the existing `skip_if_no_network!` helper so they follow
the same gating convention as the rest of the suite

## Testing
- `just fmt`


------
[Codex
Task](https://chatgpt.com/codex/tasks/task_i_69112d4cb9f08321ba773e8ccf39778e)
2025-11-13 17:11:43 -08:00
Ahmed Ibrahim
9890ceb939 Avoid double truncation (#6631)
1. Avoid double truncation by giving 10% above the tool default constant
2. Add tests that fails when const = 1
2025-11-13 16:59:31 -08:00
pakrym-oai
7b027e7536 Revert "Revert "Overhaul shell detection and centralize command generation for unified exec"" (#6607)
Reverts openai/codex#6606
2025-11-13 16:45:17 -08:00
Owen Lin
db2aa57d73 [app-server] small fixes for JSON schema export and one-of types (#6614)
A partner is consuming our generated JSON schema bundle for app-server
and identified a few issues:
- not all polymorphic / one-of types have a type descriminator
- `"$ref": "#/definitions/v2/SandboxPolicy"` is missing
- "Option<>" is an invalid schema name, and also unnecessary

This PR:
- adds the type descriminator to the various types that are missing it
except for `SessionSource` and `SubAgentSource` because they are
serialized to disk (adding this would break backwards compat for
resume), and they should not be necessary to consume for an integration
with app-server.
- removes the special handling in `export.rs` of various types like
SandboxPolicy, which turned out to be unnecessary and incorrect
- filters out `Option<>` which was auto-generated for request params
that don't need a body

For context, we currently pull in wayyy more types than we need through
the `EventMsg` god object which we are **not** planning to expose in API
v2 (this is how I suspect `SessionSource` and `SubAgentSource` are being
pulled in). But until we have all the necessary v2 notifications in
place that will allow us to remove `EventMsg`, we will keep exporting it
for now.
2025-11-13 16:25:17 -08:00
Celia Chen
b8ec97c0ef [App-server] add new v2 events:item/reasoning/delta, item/agentMessage/delta & item/reasoning/summaryPartAdded (#6559)
core event to app server event mapping:
1. `codex/event/reasoning_content_delta` ->
`item/reasoning/summaryTextDelta`.
2. `codex/event/reasoning_raw_content_delta` ->
`item/reasoning/textDelta`
3. `codex/event/agent_message_content_delta` →
`item/agentMessage/delta`.
4. `codex/event/agent_reasoning_section_break` ->
`item/reasoning/summaryPartAdded`.

Also added a change in core to pass down content index, summary index
and item id from events.

Tested with the `git checkout owen/app_server_test_client && cargo run
-p codex-app-server-test-client -- send-message-v2 "hello"` and verified
that new events are emitted correctly.
2025-11-14 00:25:01 +00:00
Dylan Hurd
2c1b693da4 chore(core) Consolidate apply_patch tests (#6545)
## Summary
Consolidates our apply_patch tests into one suite, and ensures each test
case tests the various ways the harness supports apply_patch:
1. Freeform custom tool call
2. JSON function tool
3. Simple shell call
4. Heredoc shell call

There are a few test cases that are specific to a particular variant,
I've left those alone.

## Testing
- [x] This adds a significant number of tests
2025-11-13 15:52:39 -08:00
pakrym-oai
547be54ee8 Only list failed tests (#6619)
Makes output easier to parse
2025-11-13 13:50:33 -08:00
Dan Hernandez
b4a53aef47 feat: Add support for --add-dir to exec and TypeScript SDK (#6565)
## Summary

Adds support for specifying additional directories in the TypeScript SDK
through a new `additionalDirectories` option in `ThreadOptions`.

## Changes

- Added `additionalDirectories` parameter to `ThreadOptions` interface
- Updated `CodexExec` to accept and pass through additional directories
via the `--config` flag for `sandbox_workspace_write.writable_roots`
- Added comprehensive test coverage for the new functionality

## Test plan

- Added test case that verifies `additionalDirectories` is correctly
passed as repeated flags
- Existing tests continue to pass

---------

Co-authored-by: Claude <noreply@anthropic.com>
2025-11-13 13:47:10 -08:00
Dan Hernandez
439bc5dbbe Add AbortSignal support to TypeScript SDK (#6378)
## Summary
Adds AbortSignal support to the TypeScript SDK for canceling thread
execution using AbortController.

## Changes
- Add `signal?: AbortSignal` property to `TurnOptions` type
- Pass signal through Thread class methods to exec layer  
- Add signal parameter to `CodexExecArgs`
- Leverage Node.js native `spawn()` signal support for automatic
cancellation
- Add comprehensive test coverage (6 tests covering all abort scenarios)

## Implementation
The implementation uses Node.js's built-in AbortSignal support in
`spawn()` (available since Node v15, SDK requires >=18), which
automatically handles:
- Checking if already aborted before starting
- Killing the child process when abort is triggered
- Emitting appropriate error events
- All cleanup operations

This is a one-line change to the core implementation (`signal:
args.signal` passed to spawn), making it simple, reliable, and
maintainable.

## Usage Example
```typescript
import { Codex } from '@openai/codex-sdk';

const codex = new Codex({ apiKey: 'your-api-key' });
const thread = codex.startThread();

// Create AbortController
const controller = new AbortController();

// Run with abort signal
const resultPromise = thread.run("Your prompt here", {
  signal: controller.signal
});

// Cancel anytime
controller.abort('User requested cancellation');
```

## Testing
All tests pass (23 total across SDK):
-  Aborts when signal is already aborted (both run and runStreamed)
-  Aborts during execution/iteration
-  Completes normally when not aborted
-  Backward compatible (signal is optional)

Tests verified to fail correctly when signal support is removed (no
false positives).

---------

Co-authored-by: Claude <noreply@anthropic.com>
Co-authored-by: pakrym-oai <pakrym@openai.com>
2025-11-13 13:35:42 -08:00
pakrym-oai
c95bd345ea Enable close-stale-contributor-prs.yml workflow (#6615)
Tested on https://github.com/openai/codex/pull/3036
2025-11-13 11:50:54 -08:00
pakrym-oai
0792a7953d Update default yield time (#6610)
10s for exec and 250ms for write_stdin
2025-11-13 10:24:41 -08:00
pakrym-oai
6cda3de3a4 Close stale PRs workflow (#6594)
Closes stale PRs from OpenAI employees.
2025-11-13 10:01:51 -08:00
pakrym-oai
041d6ad902 Migrate prompt caching tests to test_codex (#6605)
To hopefully fix the flakiness
2025-11-13 09:19:38 -08:00
pakrym-oai
e6995174c1 Revert "Overhaul shell detection and centralize command generation for unified exec" (#6606)
Reverts openai/codex#6577
2025-11-13 08:43:00 -08:00
pakrym-oai
d28e912214 Overhaul shell detection and centralize command generation for unified exec (#6577)
This fixes command display for unified exec. All `cd`s and `ls`es are
now parsed.

<img width="452" height="237" alt="image"
src="https://github.com/user-attachments/assets/ce92d81f-f74c-485a-9b34-1eaa29290ec6"
/>

Deletes a ton of tests that were doing nothing from shell.rs.

---------

Co-authored-by: Pavel Krymets <pavel@krymets.com>
2025-11-13 08:28:09 -08:00
Ahmed Ibrahim
ba74cee6f7 fix model picker wrapping (#6589)
Previously the popup measured rows using the full content width while
the renderer drew them with 2 columns of padding, so at certain widths
the layout allocated too little vertical space and hid the third option.
Now both desired_height and render call a shared helper that subtracts
the padding before measuring, so the height we reserve always matches
what we draw and the menu doesn't drops entries.


https://github.com/user-attachments/assets/59058fd9-1e34-4325-b5fe-fc888dfcb6bc
2025-11-13 08:09:13 -08:00
jif-oai
2a417c47ac feat: proxy context left after compaction (#6597) 2025-11-13 16:54:03 +01:00
Dylan Hurd
8dcbd29edd chore(core) Update prompt for gpt-5.1 (#6588)
## Summary
Updates the prompt for GPT-5.1
2025-11-13 07:51:28 -08:00
pakrym-oai
34621166d5 Default to explicit medium reasoning for 5.1 (#6593) 2025-11-13 07:58:42 +00:00
pakrym-oai
e3dd362c94 Reasoning level update (#6586)
Automatically update reasoning levels when migrating between models
2025-11-13 06:24:36 +00:00
Ahmed Ibrahim
305fe73d83 copy for model migration nudge (#6585) 2025-11-13 05:56:30 +00:00
Ahmed Ibrahim
e3aaee00c8 feat: show gpt mini (#6583) 2025-11-13 05:21:00 +00:00
Ahmed Ibrahim
b1979b70a8 remove porcupine model slug (#6580) 2025-11-13 04:43:31 +00:00
Eric Traut
73ed30d7e5 Avoid hang when tool's process spawns grandchild that shares stderr/stdout (#6575)
We've received many reports of codex hanging when calling certain tools.
[Here](https://github.com/openai/codex/issues/3204) is one example. This
is likely a major cause. The problem occurs when
`consume_truncated_output` waits for `stdout` and `stderr` to be closed
once the child process terminates. This normally works fine, but it
doesn't handle the case where the child has spawned grandchild processes
that inherits `stdout` and `stderr`.

The fix was originally written by @md-oai in [this
PR](https://github.com/openai/codex/pull/1852), which has gone stale.
I've copied the original fix (which looks sound to me) and added an
integration test to prevent future regressions.
2025-11-12 20:08:12 -08:00
Ahmed Ibrahim
ad7eaa80f9 Change model picker to include gpt5.1 (#6569)
- Change the presets
- Change the tests that make sure we keep the list of tools updated
- Filter out deprecated models
2025-11-12 19:44:53 -08:00
Ahmed Ibrahim
966d71c02a Update subtitle of model picker as part of the nux (#6567) 2025-11-12 18:30:43 -08:00
pakrym-oai
f97874093e Set verbosity to low for 5.1 (#6568)
And improve test coverage
2025-11-13 01:40:52 +00:00
Ahmed Ibrahim
e63ab0dd65 NUX for gpt5.1 (#6561)
- Introducing a screen to inform users of model changes. 
- Config name is being passed to be able to reuse this component in the
future for future models
2025-11-13 01:24:21 +00:00
Owen Lin
964220ac94 [app-server] feat: thread/resume supports history, path, and overrides (#6483)
This updates `thread/resume` to be at parity with v1's
`ResumeConversationParams`. Turns out history is useful for codex cloud
and path is useful for the VSCode extension. And config overrides are
always useful.
2025-11-12 22:02:43 +00:00
pakrym-oai
2f58e69997 Do not double encode request bodies in logging (#6558) 2025-11-12 21:28:42 +00:00
pakrym-oai
ec69a4a810 Add gpt-5.1 model definitions (#6551) 2025-11-12 12:44:36 -08:00
Eric Traut
ad09c138b9 Fixed status output to use auth information from AuthManager (#6529)
This PR addresses https://github.com/openai/codex/issues/6360. The root
problem is that the TUI was directly loading the `auth.json` file to
access the auth information. It should instead be using the AuthManager,
which records the current auth information. The `auth.json` file can be
overwritten at any time by other instances of the CLI or extension, so
its information can be out of sync with the current instance. The
`/status` command should always report the auth information associated
with the current instance.

An alternative fix for this bug was submitted by @chojs23 in [this
PR](https://github.com/openai/codex/pull/6495). That approach was only a
partial fix.
2025-11-12 10:26:50 -08:00
jif-oai
e00eb50db3 feat: only wait for mutating tools for ghost commit (#6534) 2025-11-12 18:16:32 +00:00
pakrym-oai
7d9ad3effd Fix otel tests (#6541)
Mount responses only once, remove unneeded retries and add a final
assistant messages to complete the turn.
2025-11-12 16:35:34 +00:00
Michael Bolin
c3a710ee14 chore: verify boolean values can be parsed as config overrides (#6516)
This is important to ensure that this:

```
codex --enable unified_exec
```

and this:

```
codex --config features.unified_exec=true
```

are equivalent. Also that when it is passed programmatically:


807e2c27f0/codex-rs/app-server-protocol/src/protocol/v1.rs (L55)

then this should work for `config`:

```json
{"features": {"shell_command_tool": true}}
```

though I believe also this:

```json
{"features.shell_command_tool": true}
```
2025-11-12 08:19:16 -08:00
Michael Bolin
29364f3a9b feat: shell_command tool (#6510)
This adds support for a new variant of the shell tool behind a flag. To
test, run `codex` with `--enable shell_command_tool`, which will
register the tool with Codex under the name `shell_command` that accepts
the following shape:

```python
{
  command: str
  workdir: str | None,
  timeout_ms: int | None,
  with_escalated_permissions: bool | None,
  justification: str | None,
}
```

This is comparable to the existing tool registered under
`shell`/`container.exec`. The primary difference is that it accepts
`command` as a `str` instead of a `str[]`. The `shell_command` tool
executes by running `execvp(["bash", "-lc", command])`, though the exact
arguments to `execvp(3)` depend on the user's default shell.

The hypothesis is that this will simplify things for the model. For
example, on Windows, instead of generating:

```json
{"command": ["pwsh.exe", "-NoLogo", "-Command", "ls -Name"]}
```

The model could simply generate:

```json
{"command": "ls -Name"}
```

As part of this change, I extracted some logic out of `user_shell.rs` as
`Shell::derive_exec_args()` so that it can be reused in
`codex-rs/core/src/tools/handlers/shell.rs`. Note the original code
generated exec arg lists like:

```javascript
["bash", "-lc", command]
["zsh", "-lc", command]
["pwsh.exe", "-NoProfile", "-Command", command]
```

Using `-l` for Bash and Zsh, but then specifying `-NoProfile` for
PowerShell seemed inconsistent to me, so I changed this in the new
implementation while also adding a `use_login_shell: bool` option to
make this explicit. If we decide to add a `login: bool` to
`ShellCommandToolCallParams` like we have for unified exec:


807e2c27f0/codex-rs/core/src/tools/handlers/unified_exec.rs (L33-L34)

Then this should make it straightforward to support.
2025-11-12 08:18:57 -08:00
jif-oai
530db0ad73 feat: warning switch model on resume (#6507)
<img width="1259" height="40" alt="Screenshot 2025-11-11 at 14 01 41"
src="https://github.com/user-attachments/assets/48ead3d2-d89c-4d8a-a578-82d9663dbd88"
/>
2025-11-12 11:13:37 +00:00
Gabriel Peal
424bfecd0b Re-add prettier log-level=warn to generate-ts (#6528)
I added it in https://github.com/openai/codex/pull/6342 but it was
removed in
https://github.com/openai/codex/pull/5063/files#diff-e2aa6dad1e886b7765158a27aefd1be5de99baa71b44f6bc5ce3fe462b9ae5d3R135
as a result of a bad diamond merge
2025-11-11 21:30:01 -05:00
Lionel Cheng
eb1c651c00 Update full-auto description with on-request (#6523)
This PR fixes #6522 by correcting the comment for `full-auto` in both
`codex-rs/exec/src/cli.rs` and `codex-rs/tui/src/cli.rs` from `-a
on-failure` to `-a on-request` to make it coherent with
`codex-rs/tui/src/lib.rs:97-105`:

```rust
pub async fn run_main(
    mut cli: Cli,
    codex_linux_sandbox_exe: Option<PathBuf>,
) -> std::io::Result<AppExitInfo> {
    let (sandbox_mode, approval_policy) = if cli.full_auto {
        (
            Some(SandboxMode::WorkspaceWrite),
            Some(AskForApproval::OnRequest),
        )
```

Running `just codex --help` or `just codex exec --help` should now yield
the correct description of `full-auto` CLI argument.

Signed-off-by: lionelchg <lionel.cheng@hotmail.fr>
2025-11-11 15:59:20 -08:00
Celia Chen
e357fc723d [app-server] add item started/completed events for turn items (#6517)
This one should be quite straightforward, as it's just a translation of
TurnItem events we already emit to ThreadItem that app-server exposes to
customers.

To test, cp my change to owen/app_server_test_client and do the
following:
```
cargo build -p codex-cli
RUST_LOG=codex_app_server=info CODEX_BIN=target/debug/codex cargo run -p codex-app-server-test-client -- send-message-v2 "hello"
```

example event before (still kept there for backward compatibility):
```
{
<   "method": "codex/event/item_completed",
<   "params": {
<     "conversationId": "019a74cc-fad9-7ab3-83a3-f42827b7b074",
<     "id": "0",
<     "msg": {
<       "item": {
<         "Reasoning": {
<           "id": "rs_03d183492e07e20a016913a936eb8c81a1a7671a103fee8afc",
<           "raw_content": [],
<           "summary_text": [
<             "Hey! What would you like to work on? I can explore the repo, run specific tests, or implement a change. Let's keep it short and straightforward. There's no need for a lengthy introduction or elaborate planning, just a friendly greeting and an open offer to help. I want to make sure the user feels welcomed and understood right from the start. It's all about keeping the tone friendly and concise!"
<           ]
<         }
<       },
<       "thread_id": "019a74cc-fad9-7ab3-83a3-f42827b7b074",
<       "turn_id": "0",
<       "type": "item_completed"
<     }
<   }
< }
```

after (v2):
```
< {
<   "method": "item/completed",
<   "params": {
<     "item": {
<       "id": "rs_03d183492e07e20a016913a936eb8c81a1a7671a103fee8afc",
<       "text": "Hey! What would you like to work on? I can explore the repo, run specific tests, or implement a change. Let's keep it short and straightforward. There's no need for a lengthy introduction or elaborate planning, just a friendly greeting and an open offer to help. I want to make sure the user feels welcomed and understood right from the start. It's all about keeping the tone friendly and concise!",
<       "type": "reasoning"
<     }
<   }
< }
```
2025-11-11 22:43:24 +00:00
pakrym-oai
807e2c27f0 Add unified exec escalation handling and tests (#6492)
Similar implementation to the shell tool
2025-11-11 08:19:35 -08:00
jif-oai
ad279eacdc nit: logs to trace (#6503) 2025-11-11 13:37:06 +00:00
jif-oai
052b052832 Enable ghost_commit feature by default (#6041)
## Summary
- enable the ghost_commit feature flag by default

## Testing
- just fmt

------
https://chatgpt.com/codex/tasks/task_i_6904ce2d0370832dbb3c2c09a90fb188
2025-11-11 09:20:46 +00:00
Celia Chen
6951872776 [hygiene][app-server] have a helper function for duplicate code in turn APIs (#6488)
turn_start and turn_interrupt have some logic that can be shared. have a
helper function for it.
2025-11-11 02:44:47 +00:00
pakrym-oai
bb7b0213a8 Colocate more of bash parsing (#6489)
Move a few callsites that were detecting `bash -lc` into a shared
helper.
2025-11-11 02:38:36 +00:00
pakrym-oai
6c36318bd8 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
2025-11-10 17:17:09 -08:00
zhao-oai
930f81a17b flip rate limit status bar (#6482)
flipping rate limit status bar to match chat.com/codex/settings/usage

<img width="848" height="420" alt="Screenshot 2025-11-10 at 4 53 41 PM"
src="https://github.com/user-attachments/assets/e326db3f-4405-412d-9e62-337282ec9a35"
/>
2025-11-11 01:13:10 +00:00
iceweasel-oai
9aff64e017 upload Windows .exe file artifacts for CLI releases (#6478)
This PR is to unlock future WinGet installation. WinGet struggles to
create command aliases when installing from nested ZIPs on some clients,
so adding raw Windows x64/Arm64 executables lets the manifest use
InstallerType: portable with direct EXEs, which reliably registers the
codex alias. This makes “winget install → codex” work out of the box
without PATH changes.

---------

Co-authored-by: Michael Bolin <mbolin@openai.com>
2025-11-10 23:31:06 +00:00
Owen Lin
3838d6739c [app-server] update macro to make renaming methods less boilerplate-y (#6470)
We already do this for notification definitions and it's really nice.

Verified there are no changes to actual exported files by diff'ing
before and after this change.
2025-11-10 15:15:08 -08:00
Josh McKinney
60deb6773a refactor(tui): job-control for Ctrl-Z handling (#6477)
- Moved the unix-only suspend/resume logic into a dedicated job_control
module housing SuspendContext, replacing scattered cfg-gated fields and
helpers in tui.rs.
- Tui now holds a single suspend_context (Arc-backed) instead of
multiple atomics, and the event stream uses it directly for Ctrl-Z
handling.
- Added detailed docs around the suspend/resume flow, cursor tracking,
and the Arc/atomic ownership model for the 'static event stream.
- Renamed the process-level SIGTSTP helper to suspend_process and the
cursor tracker to set_cursor_y to better reflect their roles.
2025-11-10 23:13:43 +00:00
Jeremy Rose
0271c20d8f add codex debug seatbelt --log-denials (#4098)
This adds a debugging tool for analyzing why certain commands fail to
execute under the sandbox.

Example output:

```
$ codex debug seatbelt --log-denials bash -lc "(echo foo > ~/foo.txt)"
bash: /Users/nornagon/foo.txt: Operation not permitted

=== Sandbox denials ===
(bash) file-write-data /dev/tty
(bash) file-write-data /dev/ttys001
(bash) sysctl-read kern.ngroups
(bash) file-write-create /Users/nornagon/foo.txt
```

It operates by:

1. spawning `log stream` to watch system logs, and
2. tracking all descendant PIDs using kqueue + proc_listchildpids.

this is a "best-effort" technique, as `log stream` may drop logs(?), and
kqueue + proc_listchildpids isn't atomic and can end up missing very
short-lived processes. But it works well enough in my testing to be
useful :)
2025-11-10 22:48:14 +00:00
George Nesterenok
52e97b9b6b Fix wayland image paste error (#4824)
## Summary
- log and surface clipboard failures instead of silently ignoring them
when `Ctrl+V` pastes an image (`paste_image_to_temp_png()` now feeds an
error history cell)
- enable `arboard`’s `wayland-data-control` feature so native Wayland
sessions can deliver image data without XWayland
- keep the success path unchanged: valid images still attach and show
the `[image …]` placeholder as before

Fixes #4818

---------

Co-authored-by: Eric Traut <etraut@openai.com>
Co-authored-by: Jeremy Rose <172423086+nornagon-openai@users.noreply.github.com>
2025-11-10 14:35:30 -08:00
Owen Lin
2ac49fea58 [app-server] chore: move initialize out of deprecated API section (#6468)
Self-explanatory - `initialize` is not a deprecated API and works
equally well with the v2 APIs.
2025-11-10 20:24:36 +00:00
jif-oai
f01f2ec9ee feat: add workdir to unified_exec (#6466) 2025-11-10 19:53:36 +00:00
zhao-oai
980886498c Add user command event types (#6246)
adding new user command event, logic in TUI to render user command
events
2025-11-10 19:18:45 +00:00
Ahmed Ibrahim
e743d251a7 Add opt-out for rate limit model nudge (#6433)
## Summary
- add a `hide_rate_limit_model_nudge` notice flag plus config edit
plumbing so the rate limit reminder preference is persisted and
documented
- extend the chat widget prompt with a "never show again" option, and
wire new app events so selecting it hides future nudges immediately and
writes the config
- add unit coverage and refresh the snapshot for the three-option prompt

## Testing
- `just fmt`
- `just fix -p codex-tui`
- `just fix -p codex-core`
- `cargo test -p codex-tui`
- `cargo test -p codex-core` *(fails at
`exec::tests::kill_child_process_group_kills_grandchildren_on_timeout`:
grandchild process still alive)*

------
[Codex
Task](https://chatgpt.com/codex/tasks/task_i_6910d7f407748321b2661fc355416994)
2025-11-10 09:21:53 -08:00
Shijie Rao
788badd221 fix: update brew auto update version check (#6238)
### Summary
* Use
`https://github.com/Homebrew/homebrew-cask/blob/main/Casks/c/codex.rb`
to get the latest available version for brew usage.
2025-11-10 09:05:00 -08:00
Owen Lin
fbdedd9a06 [app-server] feat: add command to generate json schema (#6406)
Add a `codex generate-json-schema` command for generating a JSON schema
bundle of app-server types, analogous to the existing `codex
generate-ts` command for Typescript.
2025-11-10 16:59:14 +00:00
Eric Traut
5916153157 Don't lock PRs that have been closed without merging (#6422)
The CLA action is designed to automatically lock a PR when it is closed.
This preserves the CLA agreement statements, preventing the contributor
from deleting them after the fact. However, this action is currently
locking PRs that are closed without merging. I'd like to keep such PRs
open so the contributor can respond with additional comments. I'm
currently manually unlocking PRs that I close, but I'd like to eliminate
this manual step.
2025-11-10 08:46:11 -08:00
Eric Traut
b46012e483 Support exiting from the login menu (#6419)
I recently fixed a bug in [this
PR](https://github.com/openai/codex/pull/6285) that prevented Ctrl+C
from dismissing the login menu in the TUI and leaving the user unauthed.

A [user pointed out](https://github.com/openai/codex/issues/6418) that
this makes Ctrl+C can no longer be used to exit the app. This PR changes
the behavior so we exit the app rather than ignoring the Ctrl+C.
2025-11-10 08:43:11 -08:00
Owen Lin
42683dadfb fix: use generate_ts from app_server_protocol (#6407)
Update `codex generate-ts` to use the TS export code from
`app-server-protocol/src/export.rs`.

I realized there were two duplicate implementations of Typescript export
code:
- `app-server-protocol/src/export.rs`
- the `codex-protocol-ts` crate

The `codex-protocol-ts` crate that `codex generate-ts` uses is out of
date now since it doesn't handle the V2 namespace from:
https://github.com/openai/codex/pull/6212.
2025-11-10 08:08:12 -08:00
Eric Traut
65cb1a1b77 Updated docs to reflect recent changes in web_search configuration (#6376)
This is a simplified version of [a
PR](https://github.com/openai/codex/pull/6134) supplied by a community
member.

It updates the docs to reflect a recent config deprecation.
2025-11-10 07:57:56 -08:00
jif-oai
50a77dc138 Move compact (#6454) 2025-11-10 11:59:48 +00:00
Raduan A.
557ac63094 Fix config documentation: correct TOML parsing description (#6424)
The CLI help text and inline comments incorrectly stated that -c
key=value flag parses values as JSON, when the implementation actually
uses TOML parsing via parse_toml_value(). This caused confusion when
users attempted to configure MCP servers using JSON syntax based on the
documentation.

Changes:
- Updated help text to correctly state TOML parsing instead of JSON

Fixes #4531
2025-11-09 22:58:32 -08:00
Andrew Nikolin
131c384361 Fix warning message phrasing (#6446)
Small fix for sentence phrasing in the warning message

Co-authored-by: AndrewNikolin <877163+AndrewNikolin@users.noreply.github.com>
2025-11-09 22:12:28 -08:00
dependabot[bot]
e2598f5094 chore(deps): bump zeroize from 1.8.1 to 1.8.2 in /codex-rs (#6444)
Bumps [zeroize](https://github.com/RustCrypto/utils) from 1.8.1 to
1.8.2.
<details>
<summary>Commits</summary>
<ul>
<li><a
href="c100874101"><code>c100874</code></a>
zeroize v1.8.2 (<a
href="https://redirect.github.com/RustCrypto/utils/issues/1229">#1229</a>)</li>
<li><a
href="3940ccbebd"><code>3940ccb</code></a>
Switch from <code>doc_auto_cfg</code> to <code>doc_cfg</code> (<a
href="https://redirect.github.com/RustCrypto/utils/issues/1228">#1228</a>)</li>
<li><a
href="c68a5204b2"><code>c68a520</code></a>
Fix Nightly warnings (<a
href="https://redirect.github.com/RustCrypto/utils/issues/1080">#1080</a>)</li>
<li><a
href="b15cc6c1cd"><code>b15cc6c</code></a>
cargo: point <code>repository</code> metadata to clonable URLs (<a
href="https://redirect.github.com/RustCrypto/utils/issues/1079">#1079</a>)</li>
<li><a
href="3db6690f7b"><code>3db6690</code></a>
zeroize: fix <code>homepage</code>/<code>repository</code> in Cargo.toml
(<a
href="https://redirect.github.com/RustCrypto/utils/issues/1076">#1076</a>)</li>
<li>See full diff in <a
href="https://github.com/RustCrypto/utils/compare/zeroize-v1.8.1...zeroize-v1.8.2">compare
view</a></li>
</ul>
</details>
<br />


[![Dependabot compatibility
score](https://dependabot-badges.githubapp.com/badges/compatibility_score?dependency-name=zeroize&package-manager=cargo&previous-version=1.8.1&new-version=1.8.2)](https://docs.github.com/en/github/managing-security-vulnerabilities/about-dependabot-security-updates#about-compatibility-scores)

Dependabot will resolve any conflicts with this PR as long as you don't
alter it yourself. You can also trigger a rebase manually by commenting
`@dependabot rebase`.

[//]: # (dependabot-automerge-start)
[//]: # (dependabot-automerge-end)

---

<details>
<summary>Dependabot commands and options</summary>
<br />

You can trigger Dependabot actions by commenting on this PR:
- `@dependabot rebase` will rebase this PR
- `@dependabot recreate` will recreate this PR, overwriting any edits
that have been made to it
- `@dependabot merge` will merge this PR after your CI passes on it
- `@dependabot squash and merge` will squash and merge this PR after
your CI passes on it
- `@dependabot cancel merge` will cancel a previously requested merge
and block automerging
- `@dependabot reopen` will reopen this PR if it is closed
- `@dependabot close` will close this PR and stop Dependabot recreating
it. You can achieve the same result by closing it manually
- `@dependabot show <dependency name> ignore conditions` will show all
of the ignore conditions of the specified dependency
- `@dependabot ignore this major version` will close this PR and stop
Dependabot creating any more for this major version (unless you reopen
the PR or upgrade to it yourself)
- `@dependabot ignore this minor version` will close this PR and stop
Dependabot creating any more for this minor version (unless you reopen
the PR or upgrade to it yourself)
- `@dependabot ignore this dependency` will close this PR and stop
Dependabot creating any more for this dependency (unless you reopen the
PR or upgrade to it yourself)


</details>

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-11-09 22:03:36 -08:00
dependabot[bot]
78b2aeea55 chore(deps): bump askama from 0.12.1 to 0.14.0 in /codex-rs (#6443)
Bumps [askama](https://github.com/askama-rs/askama) from 0.12.1 to
0.14.0.
<details>
<summary>Release notes</summary>
<p><em>Sourced from <a
href="https://github.com/askama-rs/askama/releases">askama's
releases</a>.</em></p>
<blockquote>
<h2>v0.14.0</h2>
<h2>Added Features</h2>
<ul>
<li>Implement <code>Values</code> on tuple by <a
href="https://github.com/GuillaumeGomez"><code>@​GuillaumeGomez</code></a>
in <a
href="https://redirect.github.com/askama-rs/askama/pull/391">askama-rs/askama#391</a></li>
<li>Pass variables to sub-templates more reliably even if indirectly by
<a href="https://github.com/Kijewski"><code>@​Kijewski</code></a> in <a
href="https://redirect.github.com/askama-rs/askama/pull/397">askama-rs/askama#397</a></li>
<li>Implement <code>first</code> and <code>blank</code> arguments for
<code>|indent</code> by <a
href="https://github.com/Kijewski"><code>@​Kijewski</code></a> in <a
href="https://redirect.github.com/askama-rs/askama/pull/401">askama-rs/askama#401</a></li>
<li>Add named arguments for builtin filters by <a
href="https://github.com/Kijewski"><code>@​Kijewski</code></a> in <a
href="https://redirect.github.com/askama-rs/askama/pull/403">askama-rs/askama#403</a></li>
<li>Add <code>unique</code> filter by <a
href="https://github.com/GuillaumeGomez"><code>@​GuillaumeGomez</code></a>
in <a
href="https://redirect.github.com/askama-rs/askama/pull/405">askama-rs/askama#405</a></li>
</ul>
<h2>Bug Fixes And Consistency</h2>
<ul>
<li><code>askama_derive</code> accidentally exposed as a feature by <a
href="https://github.com/Kijewski"><code>@​Kijewski</code></a> in <a
href="https://redirect.github.com/askama-rs/askama/pull/384">askama-rs/askama#384</a></li>
<li>Track config files by <a
href="https://github.com/GuillaumeGomez"><code>@​GuillaumeGomez</code></a>
in <a
href="https://redirect.github.com/askama-rs/askama/pull/385">askama-rs/askama#385</a></li>
<li>If using local variable as value when creating a new variable, do
not put it behind a reference by <a
href="https://github.com/GuillaumeGomez"><code>@​GuillaumeGomez</code></a>
in <a
href="https://redirect.github.com/askama-rs/askama/pull/392">askama-rs/askama#392</a></li>
<li>generator: make <code>CARGO_MANIFEST_DIR</code> part of
<code>ConfigKey</code> by <a
href="https://github.com/strickczq"><code>@​strickczq</code></a> in <a
href="https://redirect.github.com/askama-rs/askama/pull/395">askama-rs/askama#395</a></li>
<li>Do not put question mark initialization expressions behind a
reference by <a
href="https://github.com/GuillaumeGomez"><code>@​GuillaumeGomez</code></a>
in <a
href="https://redirect.github.com/askama-rs/askama/pull/400">askama-rs/askama#400</a></li>
<li>Update to more current rust version on readthedocs by <a
href="https://github.com/Kijewski"><code>@​Kijewski</code></a> in <a
href="https://redirect.github.com/askama-rs/askama/pull/410">askama-rs/askama#410</a></li>
<li>Fix <code>unique</code> filter implementation by <a
href="https://github.com/GuillaumeGomez"><code>@​GuillaumeGomez</code></a>
in <a
href="https://redirect.github.com/askama-rs/askama/pull/417">askama-rs/askama#417</a></li>
<li>Add <code>|titlecase</code> as alias for <code>|title</code> by <a
href="https://github.com/Kijewski"><code>@​Kijewski</code></a> in <a
href="https://redirect.github.com/askama-rs/askama/pull/416">askama-rs/askama#416</a></li>
</ul>
<h2>Further Changes</h2>
<ul>
<li>book: add page about <code>FastWritable</code> by <a
href="https://github.com/Kijewski"><code>@​Kijewski</code></a> in <a
href="https://redirect.github.com/askama-rs/askama/pull/407">askama-rs/askama#407</a></li>
<li>Add throughput to derive benchmark by <a
href="https://github.com/Kijewski"><code>@​Kijewski</code></a> in <a
href="https://redirect.github.com/askama-rs/askama/pull/413">askama-rs/askama#413</a></li>
<li>Move <code>FastWritable</code> into <code>askama</code> root by <a
href="https://github.com/GuillaumeGomez"><code>@​GuillaumeGomez</code></a>
in <a
href="https://redirect.github.com/askama-rs/askama/pull/411">askama-rs/askama#411</a></li>
</ul>
<h2>New Contributors</h2>
<ul>
<li><a href="https://github.com/strickczq"><code>@​strickczq</code></a>
made their first contribution in <a
href="https://redirect.github.com/askama-rs/askama/pull/395">askama-rs/askama#395</a></li>
</ul>
<p><strong>Full Changelog</strong>: <a
href="https://github.com/askama-rs/askama/compare/v0.13.0...v0.14.0">https://github.com/askama-rs/askama/compare/v0.13.0...v0.14.0</a></p>
<h2>v0.13.1</h2>
<h2>What's Changed</h2>
<ul>
<li><code>askama_derive</code> accidentally exposed as a feature by <a
href="https://github.com/Kijewski"><code>@​Kijewski</code></a> in <a
href="https://redirect.github.com/askama-rs/askama/pull/384">askama-rs/askama#384</a></li>
<li>Track config files by <a
href="https://github.com/GuillaumeGomez"><code>@​GuillaumeGomez</code></a>
in <a
href="https://redirect.github.com/askama-rs/askama/pull/385">askama-rs/askama#385</a></li>
<li>Implement <code>Values</code> on tuple by <a
href="https://github.com/GuillaumeGomez"><code>@​GuillaumeGomez</code></a>
in <a
href="https://redirect.github.com/askama-rs/askama/pull/391">askama-rs/askama#391</a></li>
<li>generator: make <code>CARGO_MANIFEST_DIR</code> part of
<code>ConfigKey</code> by <a
href="https://github.com/strickczq"><code>@​strickczq</code></a> in <a
href="https://redirect.github.com/askama-rs/askama/pull/395">askama-rs/askama#395</a></li>
</ul>
<h2>New Contributors</h2>
<ul>
<li><a href="https://github.com/strickczq"><code>@​strickczq</code></a>
made their first contribution in <a
href="https://redirect.github.com/askama-rs/askama/pull/395">askama-rs/askama#395</a></li>
</ul>
<p><strong>Full Changelog</strong>: <a
href="https://github.com/askama-rs/askama/compare/v0.13.0...v0.13.1">https://github.com/askama-rs/askama/compare/v0.13.0...v0.13.1</a></p>
<h2>v0.13.0 – Rinja is Askama, again!</h2>
<p>With this release, the <a
href="https://blog.guillaume-gomez.fr/articles/2024-07-31+docs.rs+switching+jinja+template+framework+from+tera+to+rinja">fork</a>
rinja got merged back into the main project. Please have a look at our
<a
href="https://blog.guillaume-gomez.fr/articles/2025-03-19+Askama+and+Rinja+merge">blog
post</a> for more information about the split and the merge.</p>
<h2>What's Changed</h2>
<p>This release (v0.13.0), when <a
href="https://github.com/askama-rs/askama/compare/0.12.1...v0.13.0">compared
to</a> the last stable askama release (v0.12.1), consists of:</p>
<ul>
<li>over 1000 commits</li>
<li>with changes in over 500 files</li>
<li>with over 40k additions and 8000 deletions</li>
</ul>
<!-- raw HTML omitted -->
</blockquote>
<p>... (truncated)</p>
</details>
<details>
<summary>Commits</summary>
<ul>
<li><a
href="95867ac8ce"><code>95867ac</code></a>
Merge pull request <a
href="https://redirect.github.com/askama-rs/askama/issues/416">#416</a>
from Kijewski/pr-upgrading-0.14</li>
<li><a
href="61b7422497"><code>61b7422</code></a>
Add <code>|titlecase</code> as alias for <code>|title</code></li>
<li><a
href="79be271593"><code>79be271</code></a>
Run doctests</li>
<li><a
href="72bbe3ede1"><code>72bbe3e</code></a>
Bump version number to v0.14.0</li>
<li><a
href="57750338fa"><code>5775033</code></a>
book: update <code>upgrading.md</code></li>
<li><a
href="a5b43c0aa2"><code>a5b43c0</code></a>
Fix <code>unique</code> filter implementation</li>
<li><a
href="7fccbdf1d7"><code>7fccbdf</code></a>
Remove usage of <code>nextest</code></li>
<li><a
href="6a16256f24"><code>6a16256</code></a>
Fix new clippy lints</li>
<li><a
href="04a4d5b020"><code>04a4d5b</code></a>
Update MSRV to 1.83</li>
<li><a
href="d2a788a740"><code>d2a788a</code></a>
Add doc about <code>unique</code> filter</li>
<li>Additional commits viewable in <a
href="https://github.com/askama-rs/askama/compare/0.12.1...v0.14.0">compare
view</a></li>
</ul>
</details>
<br />


[![Dependabot compatibility
score](https://dependabot-badges.githubapp.com/badges/compatibility_score?dependency-name=askama&package-manager=cargo&previous-version=0.12.1&new-version=0.14.0)](https://docs.github.com/en/github/managing-security-vulnerabilities/about-dependabot-security-updates#about-compatibility-scores)

Dependabot will resolve any conflicts with this PR as long as you don't
alter it yourself. You can also trigger a rebase manually by commenting
`@dependabot rebase`.

[//]: # (dependabot-automerge-start)
[//]: # (dependabot-automerge-end)

---

<details>
<summary>Dependabot commands and options</summary>
<br />

You can trigger Dependabot actions by commenting on this PR:
- `@dependabot rebase` will rebase this PR
- `@dependabot recreate` will recreate this PR, overwriting any edits
that have been made to it
- `@dependabot merge` will merge this PR after your CI passes on it
- `@dependabot squash and merge` will squash and merge this PR after
your CI passes on it
- `@dependabot cancel merge` will cancel a previously requested merge
and block automerging
- `@dependabot reopen` will reopen this PR if it is closed
- `@dependabot close` will close this PR and stop Dependabot recreating
it. You can achieve the same result by closing it manually
- `@dependabot show <dependency name> ignore conditions` will show all
of the ignore conditions of the specified dependency
- `@dependabot ignore this major version` will close this PR and stop
Dependabot creating any more for this major version (unless you reopen
the PR or upgrade to it yourself)
- `@dependabot ignore this minor version` will close this PR and stop
Dependabot creating any more for this minor version (unless you reopen
the PR or upgrade to it yourself)
- `@dependabot ignore this dependency` will close this PR and stop
Dependabot creating any more for this dependency (unless you reopen the
PR or upgrade to it yourself)


</details>

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-11-09 22:02:26 -08:00
dependabot[bot]
082d2fa19a chore(deps): bump taiki-e/install-action from 2.60.0 to 2.62.49 (#6438)
Bumps
[taiki-e/install-action](https://github.com/taiki-e/install-action) from
2.60.0 to 2.62.49.
<details>
<summary>Release notes</summary>
<p><em>Sourced from <a
href="https://github.com/taiki-e/install-action/releases">taiki-e/install-action's
releases</a>.</em></p>
<blockquote>
<h2>2.62.49</h2>
<ul>
<li>
<p>Update <code>cargo-binstall@latest</code> to 1.15.11.</p>
</li>
<li>
<p>Update <code>cargo-auditable@latest</code> to 0.7.2.</p>
</li>
<li>
<p>Update <code>vacuum@latest</code> to 0.20.2.</p>
</li>
</ul>
<h2>2.62.48</h2>
<ul>
<li>
<p>Update <code>mise@latest</code> to 2025.11.3.</p>
</li>
<li>
<p>Update <code>cargo-audit@latest</code> to 0.22.0.</p>
</li>
<li>
<p>Update <code>vacuum@latest</code> to 0.20.1.</p>
</li>
<li>
<p>Update <code>uv@latest</code> to 0.9.8.</p>
</li>
<li>
<p>Update <code>cargo-udeps@latest</code> to 0.1.60.</p>
</li>
<li>
<p>Update <code>zizmor@latest</code> to 1.16.3.</p>
</li>
</ul>
<h2>2.62.47</h2>
<ul>
<li>
<p>Update <code>vacuum@latest</code> to 0.20.0.</p>
</li>
<li>
<p>Update <code>cargo-nextest@latest</code> to 0.9.111.</p>
</li>
<li>
<p>Update <code>cargo-shear@latest</code> to 1.6.2.</p>
</li>
</ul>
<h2>2.62.46</h2>
<ul>
<li>
<p>Update <code>vacuum@latest</code> to 0.19.5.</p>
</li>
<li>
<p>Update <code>syft@latest</code> to 1.37.0.</p>
</li>
<li>
<p>Update <code>mise@latest</code> to 2025.11.2.</p>
</li>
<li>
<p>Update <code>knope@latest</code> to 0.21.5.</p>
</li>
</ul>
<h2>2.62.45</h2>
<ul>
<li>
<p>Update <code>zizmor@latest</code> to 1.16.2.</p>
</li>
<li>
<p>Update <code>cargo-binstall@latest</code> to 1.15.10.</p>
</li>
<li>
<p>Update <code>ubi@latest</code> to 0.8.4.</p>
</li>
<li>
<p>Update <code>mise@latest</code> to 2025.11.1.</p>
</li>
<li>
<p>Update <code>cargo-semver-checks@latest</code> to 0.45.0.</p>
</li>
</ul>
<h2>2.62.44</h2>
<ul>
<li>Update <code>mise@latest</code> to 2025.11.0.</li>
</ul>
<!-- raw HTML omitted -->
</blockquote>
<p>... (truncated)</p>
</details>
<details>
<summary>Changelog</summary>
<p><em>Sourced from <a
href="https://github.com/taiki-e/install-action/blob/main/CHANGELOG.md">taiki-e/install-action's
changelog</a>.</em></p>
<blockquote>
<h1>Changelog</h1>
<p>All notable changes to this project will be documented in this
file.</p>
<p>This project adheres to <a href="https://semver.org">Semantic
Versioning</a>.</p>
<!-- raw HTML omitted -->
<h2>[Unreleased]</h2>
<h2>[2.62.49] - 2025-11-09</h2>
<ul>
<li>
<p>Update <code>cargo-binstall@latest</code> to 1.15.11.</p>
</li>
<li>
<p>Update <code>cargo-auditable@latest</code> to 0.7.2.</p>
</li>
<li>
<p>Update <code>vacuum@latest</code> to 0.20.2.</p>
</li>
</ul>
<h2>[2.62.48] - 2025-11-08</h2>
<ul>
<li>
<p>Update <code>mise@latest</code> to 2025.11.3.</p>
</li>
<li>
<p>Update <code>cargo-audit@latest</code> to 0.22.0.</p>
</li>
<li>
<p>Update <code>vacuum@latest</code> to 0.20.1.</p>
</li>
<li>
<p>Update <code>uv@latest</code> to 0.9.8.</p>
</li>
<li>
<p>Update <code>cargo-udeps@latest</code> to 0.1.60.</p>
</li>
<li>
<p>Update <code>zizmor@latest</code> to 1.16.3.</p>
</li>
</ul>
<h2>[2.62.47] - 2025-11-05</h2>
<ul>
<li>
<p>Update <code>vacuum@latest</code> to 0.20.0.</p>
</li>
<li>
<p>Update <code>cargo-nextest@latest</code> to 0.9.111.</p>
</li>
<li>
<p>Update <code>cargo-shear@latest</code> to 1.6.2.</p>
</li>
</ul>
<h2>[2.62.46] - 2025-11-04</h2>
<ul>
<li>
<p>Update <code>vacuum@latest</code> to 0.19.5.</p>
</li>
<li>
<p>Update <code>syft@latest</code> to 1.37.0.</p>
</li>
<li>
<p>Update <code>mise@latest</code> to 2025.11.2.</p>
</li>
</ul>
<!-- raw HTML omitted -->
</blockquote>
<p>... (truncated)</p>
</details>
<details>
<summary>Commits</summary>
<ul>
<li><a
href="44c6d64aa6"><code>44c6d64</code></a>
Release 2.62.49</li>
<li><a
href="3a701df4c2"><code>3a701df</code></a>
Update <code>cargo-binstall@latest</code> to 1.15.11</li>
<li><a
href="4242e04eb8"><code>4242e04</code></a>
Update <code>cargo-auditable@latest</code> to 0.7.2</li>
<li><a
href="3df5533ef8"><code>3df5533</code></a>
Update <code>vacuum@latest</code> to 0.20.2</li>
<li><a
href="e797ba6a25"><code>e797ba6</code></a>
Release 2.62.48</li>
<li><a
href="bcf91e02ac"><code>bcf91e0</code></a>
Update <code>mise@latest</code> to 2025.11.3</li>
<li><a
href="e78113b60c"><code>e78113b</code></a>
Update <code>cargo-audit@latest</code> to 0.22.0</li>
<li><a
href="0ef486444e"><code>0ef4864</code></a>
Update <code>vacuum@latest</code> to 0.20.1</li>
<li><a
href="5eda7b1985"><code>5eda7b1</code></a>
Update <code>uv@latest</code> to 0.9.8</li>
<li><a
href="3853a413e6"><code>3853a41</code></a>
Update <code>cargo-udeps@latest</code> to 0.1.60</li>
<li>Additional commits viewable in <a
href="0c5db7f7f8...44c6d64aa6">compare
view</a></li>
</ul>
</details>
<br />


[![Dependabot compatibility
score](https://dependabot-badges.githubapp.com/badges/compatibility_score?dependency-name=taiki-e/install-action&package-manager=github_actions&previous-version=2.60.0&new-version=2.62.49)](https://docs.github.com/en/github/managing-security-vulnerabilities/about-dependabot-security-updates#about-compatibility-scores)

Dependabot will resolve any conflicts with this PR as long as you don't
alter it yourself. You can also trigger a rebase manually by commenting
`@dependabot rebase`.

[//]: # (dependabot-automerge-start)
[//]: # (dependabot-automerge-end)

---

<details>
<summary>Dependabot commands and options</summary>
<br />

You can trigger Dependabot actions by commenting on this PR:
- `@dependabot rebase` will rebase this PR
- `@dependabot recreate` will recreate this PR, overwriting any edits
that have been made to it
- `@dependabot merge` will merge this PR after your CI passes on it
- `@dependabot squash and merge` will squash and merge this PR after
your CI passes on it
- `@dependabot cancel merge` will cancel a previously requested merge
and block automerging
- `@dependabot reopen` will reopen this PR if it is closed
- `@dependabot close` will close this PR and stop Dependabot recreating
it. You can achieve the same result by closing it manually
- `@dependabot show <dependency name> ignore conditions` will show all
of the ignore conditions of the specified dependency
- `@dependabot ignore this major version` will close this PR and stop
Dependabot creating any more for this major version (unless you reopen
the PR or upgrade to it yourself)
- `@dependabot ignore this minor version` will close this PR and stop
Dependabot creating any more for this minor version (unless you reopen
the PR or upgrade to it yourself)
- `@dependabot ignore this dependency` will close this PR and stop
Dependabot creating any more for this dependency (unless you reopen the
PR or upgrade to it yourself)


</details>

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-11-09 22:00:08 -08:00
dependabot[bot]
7c7c7567d5 chore(deps): bump codespell-project/actions-codespell from 2.1 to 2.2 (#6437)
Bumps
[codespell-project/actions-codespell](https://github.com/codespell-project/actions-codespell)
from 2.1 to 2.2.
<details>
<summary>Release notes</summary>
<p><em>Sourced from <a
href="https://github.com/codespell-project/actions-codespell/releases">codespell-project/actions-codespell's
releases</a>.</em></p>
<blockquote>
<h2>v2.2</h2>
<!-- raw HTML omitted -->
<h2>What's Changed</h2>
<ul>
<li>Add the config file option and tests by <a
href="https://github.com/rdimaio"><code>@​rdimaio</code></a> in <a
href="https://redirect.github.com/codespell-project/actions-codespell/pull/80">codespell-project/actions-codespell#80</a></li>
<li>Use <code>pip install</code> with <code>--no-cache-dir</code> in the
Dockerfile by <a
href="https://github.com/PeterDaveHello"><code>@​PeterDaveHello</code></a>
in <a
href="https://redirect.github.com/codespell-project/actions-codespell/pull/89">codespell-project/actions-codespell#89</a></li>
<li>Upgrade to Python 3.13 by <a
href="https://github.com/candrews"><code>@​candrews</code></a> in <a
href="https://redirect.github.com/codespell-project/actions-codespell/pull/82">codespell-project/actions-codespell#82</a></li>
<li>Add checkout action and problem matcher to README by <a
href="https://github.com/vadi2"><code>@​vadi2</code></a> in <a
href="https://redirect.github.com/codespell-project/actions-codespell/pull/32">codespell-project/actions-codespell#32</a></li>
</ul>
<h2>New Contributors</h2>
<ul>
<li><a href="https://github.com/rdimaio"><code>@​rdimaio</code></a> made
their first contribution in <a
href="https://redirect.github.com/codespell-project/actions-codespell/pull/80">codespell-project/actions-codespell#80</a></li>
<li><a
href="https://github.com/PeterDaveHello"><code>@​PeterDaveHello</code></a>
made their first contribution in <a
href="https://redirect.github.com/codespell-project/actions-codespell/pull/89">codespell-project/actions-codespell#89</a></li>
<li><a href="https://github.com/candrews"><code>@​candrews</code></a>
made their first contribution in <a
href="https://redirect.github.com/codespell-project/actions-codespell/pull/82">codespell-project/actions-codespell#82</a></li>
<li><a href="https://github.com/vadi2"><code>@​vadi2</code></a> made
their first contribution in <a
href="https://redirect.github.com/codespell-project/actions-codespell/pull/32">codespell-project/actions-codespell#32</a></li>
</ul>
<p><strong>Full Changelog</strong>: <a
href="https://github.com/codespell-project/actions-codespell/compare/v2...v2.2">https://github.com/codespell-project/actions-codespell/compare/v2...v2.2</a></p>
</blockquote>
</details>
<details>
<summary>Commits</summary>
<ul>
<li><a
href="8f01853be1"><code>8f01853</code></a>
MAINT: Release notes</li>
<li><a
href="23a4abea24"><code>23a4abe</code></a>
Add checkout action and problem matcher to README (<a
href="https://redirect.github.com/codespell-project/actions-codespell/issues/32">#32</a>)</li>
<li><a
href="906f13fba1"><code>906f13f</code></a>
Upgrade to Python 3.13 (<a
href="https://redirect.github.com/codespell-project/actions-codespell/issues/82">#82</a>)</li>
<li><a
href="df0bba344d"><code>df0bba3</code></a>
[pre-commit.ci] pre-commit autoupdate (<a
href="https://redirect.github.com/codespell-project/actions-codespell/issues/85">#85</a>)</li>
<li><a
href="c460eef33e"><code>c460eef</code></a>
Use <code>pip install</code> with <code>--no-cache-dir</code> in the
Dockerfile (<a
href="https://redirect.github.com/codespell-project/actions-codespell/issues/89">#89</a>)</li>
<li><a
href="037a23a348"><code>037a23a</code></a>
Add the config file option and tests (<a
href="https://redirect.github.com/codespell-project/actions-codespell/issues/80">#80</a>)</li>
<li><a
href="8d1a4b1bd9"><code>8d1a4b1</code></a>
Bump actions/setup-python from 5 to 6 (<a
href="https://redirect.github.com/codespell-project/actions-codespell/issues/92">#92</a>)</li>
<li><a
href="71286cb40f"><code>71286cb</code></a>
Bump actions/checkout from 4 to 5 (<a
href="https://redirect.github.com/codespell-project/actions-codespell/issues/91">#91</a>)</li>
<li><a
href="fad9339798"><code>fad9339</code></a>
[pre-commit.ci] pre-commit autoupdate (<a
href="https://redirect.github.com/codespell-project/actions-codespell/issues/84">#84</a>)</li>
<li>See full diff in <a
href="406322ec52...8f01853be1">compare
view</a></li>
</ul>
</details>
<br />


[![Dependabot compatibility
score](https://dependabot-badges.githubapp.com/badges/compatibility_score?dependency-name=codespell-project/actions-codespell&package-manager=github_actions&previous-version=2.1&new-version=2.2)](https://docs.github.com/en/github/managing-security-vulnerabilities/about-dependabot-security-updates#about-compatibility-scores)

Dependabot will resolve any conflicts with this PR as long as you don't
alter it yourself. You can also trigger a rebase manually by commenting
`@dependabot rebase`.

[//]: # (dependabot-automerge-start)
[//]: # (dependabot-automerge-end)

---

<details>
<summary>Dependabot commands and options</summary>
<br />

You can trigger Dependabot actions by commenting on this PR:
- `@dependabot rebase` will rebase this PR
- `@dependabot recreate` will recreate this PR, overwriting any edits
that have been made to it
- `@dependabot merge` will merge this PR after your CI passes on it
- `@dependabot squash and merge` will squash and merge this PR after
your CI passes on it
- `@dependabot cancel merge` will cancel a previously requested merge
and block automerging
- `@dependabot reopen` will reopen this PR if it is closed
- `@dependabot close` will close this PR and stop Dependabot recreating
it. You can achieve the same result by closing it manually
- `@dependabot show <dependency name> ignore conditions` will show all
of the ignore conditions of the specified dependency
- `@dependabot ignore this major version` will close this PR and stop
Dependabot creating any more for this major version (unless you reopen
the PR or upgrade to it yourself)
- `@dependabot ignore this minor version` will close this PR and stop
Dependabot creating any more for this minor version (unless you reopen
the PR or upgrade to it yourself)
- `@dependabot ignore this dependency` will close this PR and stop
Dependabot creating any more for this dependency (unless you reopen the
PR or upgrade to it yourself)


</details>

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-11-09 21:58:51 -08:00
iceweasel-oai
625f2208c4 For npm upgrade on Windows, go through cmd.exe to get path traversal working (#6387)
On Windows, `npm` by itself does not resolve under std::process::Command
which does not consider PATHEXT to resolve it to `npm.cmd` in the PATH.
By running the npm upgrade command via cmd.exe we get proper path
semantics so it actually works.
2025-11-09 21:07:44 -08:00
kinopeee
5f1fab0e7c fix(cloud-tasks): respect cli_auth_credentials_store config (#5856)
## Problem

`codex cloud` always instantiated `AuthManager` with `File` mode,
ignoring the user's actual `cli_auth_credentials_store` setting. This
caused users with `cli_auth_credentials_store = "keyring"` (or `"auto"`)
to see "Not signed in" errors even when they had valid credentials
stored in the system keyring.

## Root cause

The code called `Config::load_from_base_config_with_overrides()` with an
empty `ConfigToml::default()`, which always returned `File` as the
default store mode instead of loading the actual user configuration.

## Solution

- **Added `util::load_cli_auth_manager()` helper**  
Properly loads user config via
`load_config_as_toml_with_cli_overrides()` and extracts the
`cli_auth_credentials_store` setting before creating `AuthManager`.

- **Updated callers**  
  - `init_backend()` - used when starting cloud tasks UI
  - `build_chatgpt_headers()` - used for API requests

## Testing

-  `just fmt`
-  `just fix -p codex-cloud-tasks`
-  `cargo test -p codex-cloud-tasks`

## Files changed

- `codex-rs/cloud-tasks/src/lib.rs`
- `codex-rs/cloud-tasks/src/util.rs`

## Verification

Users with keyring-based auth can now run `codex cloud` successfully
without "Not signed in" errors.

---------

Co-authored-by: Eric Traut <etraut@openai.com>
Co-authored-by: celia-oai <celia@openai.com>
2025-11-10 00:35:08 +00:00
Oliver Mannion
c07461e6f3 fix(seatbelt): Allow reading hw.physicalcpu (#6421)
Allow reading `hw.physicalcpu` so numpy can be imported when running in
the sandbox.
 
resolves #6420
2025-11-09 08:53:36 -08:00
Raduan A.
8b80a0a269 Fix SDK documentation: replace 'file diffs' with 'file change notifications' (#6425)
The TypeScript SDK's README incorrectly claimed that runStreamed() emits
"file diffs". However, the FileChangeItem type only contains metadata
(path, kind, status) without actual diff content.

Updated line 36 to accurately describe the SDK as providing "file change
notifications" instead of "file diffs" to match the actual
implementation in items.ts.

Fixes #5850
2025-11-09 08:37:16 -08:00
iceweasel-oai
a47181e471 more world-writable warning improvements (#6389)
3 improvements:
1. show up to 3 actual paths that are world-writable
2. do the scan/warning for Read-Only mode too, because it also applies
there
3. remove the "Cancel" option since it doesn't always apply (like on
startup)
2025-11-08 11:35:43 -08:00
Raduan A.
5beb6167c8 feat(tui): Display keyboard shortcuts inline for approval options (#5889)
Shows single-key shortcuts (y, a, n) next to approval options to make
them more discoverable. Previously these shortcuts worked but were
hidden, making the feature hard to discover.

Changes:
- "Yes, proceed" now shows "y" shortcut
- "Yes, and don't ask again" now shows "a" shortcut
- "No, and tell Codex..." continues to show "esc" shortcut

This improves UX by surfacing the quick keyboard shortcuts that were
already functional but undiscoverable in the UI.

---

Update:

added parentheses for better visual clarity 
<img width="1540" height="486" alt="CleanShot 2025-11-05 at 11 47 07@2x"
src="https://github.com/user-attachments/assets/f951c34a-9ec8-4b81-b151-7b2ccba94658"
/>

---------

Co-authored-by: Claude <noreply@anthropic.com>
Co-authored-by: Eric Traut <etraut@openai.com>
2025-11-08 09:08:42 -08:00
iceweasel-oai
917f39ec12 Improve world-writable scan (#6381)
1. scan many more directories since it's much faster than the original
implementation
2. limit overall scan time to 2s
3. skip some directories that are noisy - ApplicationData, Installer,
etc.
2025-11-07 21:28:55 -08:00
Luca King
a2fdfce02a Kill shell tool process groups on timeout (#5258)
## Summary
- launch shell tool processes in their own process group so Codex owns
the full tree
- on timeout or ctrl-c, send SIGKILL to the process group before
terminating the tracked child
- document that the default shell/unified_exec timeout remains 1000 ms

## Original Bug
Long-lived shell tool commands hang indefinitely because the timeout
handler only terminated the direct child process; any grandchildren it
spawned kept running and held the PTY open, preventing Codex from
regaining control.

## Repro Original Bug
Install next.js and run `next dev` (which is a long-running shell
process with children). On openai:main, it will cause the agent to
permanently get stuck here until human intervention. On this branch,
this command will be terminated successfully after timeout_ms which will
unblock the agent. This is a critical fix for unmonitored / lightly
monitored agents that don't have immediate human observation to unblock
them.

---------

Co-authored-by: Michael Bolin <mbolin@openai.com>
Co-authored-by: Michael Bolin <bolinfest@gmail.com>
2025-11-07 17:54:35 -08:00
pakrym-oai
91b16b8682 Don't request approval for safe commands in unified exec (#6380) 2025-11-07 16:36:04 -08:00
Alexander Smirnov
183fc8e01a core: replace Cloudflare 403 HTML with friendly message (#6252)
### Motivation

When Codex is launched from a region where Cloudflare blocks access (for
example, Russia), the CLI currently dumps Cloudflare’s entire HTML error
page. This isn’t actionable and makes it hard for users to understand
what happened. We want to detect the Cloudflare block and show a
concise, user-friendly explanation instead.

### What Changed

- Added CLOUDFLARE_BLOCKED_MESSAGE and a friendly_message() helper to
UnexpectedResponseError. Whenever we see a 403 whose body contains the
Cloudflare block notice, we now emit a single-line message (Access
blocked by Cloudflare…) while preserving the HTTP status and request id.
All other responses keep the original behaviour.
- Added two focused unit tests:
- unexpected_status_cloudflare_html_is_simplified ensures the Cloudflare
HTML case yields the friendly message.
- unexpected_status_non_html_is_unchanged confirms plain-text 403s still
return the raw body.

### Testing

- cargo build -p codex-cli
- cargo test -p codex-core
- just fix -p codex-core
- cargo test --all-features

---------

Co-authored-by: Eric Traut <etraut@openai.com>
2025-11-07 15:55:16 -08:00
Josh McKinney
9fba811764 refactor(terminal): cleanup deprecated flush logic (#6373)
Removes flush logic that was leftover to test against ratatui's flush
Cleaned up the flush logic so it's a bit more intent revealing.
DrawCommand now owns the Cells that it draws as this works around a
borrow checker problem.
2025-11-07 15:54:07 -08:00
Celia Chen
db408b9e62 [App-server] add initialization to doc (#6377)
Address comments in #6353.
2025-11-07 23:52:20 +00:00
Jakob Malmo
2eecc1a2e4 fix(wsl): normalize Windows paths during update (#6086) (#6097)
When running under WSL, the update command could receive Windows-style
absolute paths (e.g., `C:\...`) and pass them to Linux processes
unchanged, which fails because WSL expects those paths in
`/mnt/<drive>/...` form.

This patch adds a tiny helper in the CLI (`cli/src/wsl_paths.rs`) that:
- Detects WSL (`WSL_DISTRO_NAME` or `"microsoft"` in `/proc/version`)  
- Converts `X:\...` → `/mnt/x/...`  

`run_update_action` now normalizes the package-manager command and
arguments under WSL before spawning.
Non-WSL platforms are unaffected.  

Includes small unit tests for the converter.  

**Fixes:** #6086, #6084

Co-authored-by: Eric Traut <etraut@openai.com>
2025-11-07 14:49:17 -08:00
Dan Hernandez
c76528ca1f [SDK] Add network_access and web_search options to TypeScript SDK (#6367)
## Summary

This PR adds two new optional boolean fields to `ThreadOptions` in the
TypeScript SDK:

- **`networkAccess`**: Enables network access in the sandbox by setting
`sandbox_workspace_write.network_access` config
- **`webSearch`**: Enables the web search tool by setting
`tools.web_search` config

These options map to existing Codex configuration options and are
properly threaded through the SDK layers:
1. `ThreadOptions` (threadOptions.ts) - User-facing API
2. `CodexExecArgs` (exec.ts) - Internal execution args  
3. CLI flags via `--config` in the `codex exec` command

## Changes

- `sdk/typescript/src/threadOptions.ts`: Added `networkAccess` and
`webSearch` fields to `ThreadOptions` type
- `sdk/typescript/src/exec.ts`: Added fields to `CodexExecArgs` and CLI
flag generation
- `sdk/typescript/src/thread.ts`: Pass options through to exec layer

## Test Plan

- [x] Build succeeds (`pnpm build`)
- [x] Linter passes (`pnpm lint`)
- [x] Type definitions are properly exported
- [ ] Manual testing with sample code (to be done by reviewer)

---------

Co-authored-by: Claude <noreply@anthropic.com>
2025-11-07 13:19:34 -08:00
Michael Bolin
bb47f2226f feat: add --promote-alpha option to create_github_release script (#6370)
Historically, running `create_github_release --publish-release` would
always publish a new release from latest `main`, which isn't always the
best idea. We should really publish an alpha, let it bake, and then
promote it.

This PR introduces a new flag, `--promote-alpha`, which does exactly
that. It also works with `--dry-run`, so you can sanity check the commit
it will use as the base commit for the new release before running it for
real.

```shell
$ ./codex-rs/scripts/create_github_release --dry-run --promote-alpha 0.56.0-alpha.2
Publishing version 0.56.0
Running gh api GET /repos/openai/codex/git/refs/tags/rust-v0.56.0-alpha.2
Running gh api GET /repos/openai/codex/git/tags/7d4ef77bc35b011aa0c76c5cbe6cd7d3e53f1dfe
Running gh api GET /repos/openai/codex/compare/main...8b49211e67d3c863df5ecc13fc5f88516a20fa69
Would publish version 0.56.0 using base commit 62474a30e8 derived from rust-v0.56.0-alpha.2.
```
2025-11-07 20:05:22 +00:00
Jeremy Rose
c6ab92bc50 tui: add comments to tui.rs (#6369) 2025-11-07 18:17:52 +00:00
pakrym-oai
4c1a6f0ee0 Promote shell config tool to model family config (#6351) 2025-11-07 10:11:11 -08:00
Owen Lin
361d43b969 [app-server] doc: update README for threads and turns (#6368)
Self explanatory!
2025-11-07 17:02:49 +00:00
Celia Chen
2e81f1900d [App-server] Add auth v2 doc & update codex mcp interface auth section (#6353)
Added doc for auth v2 endpoints. Updated the auth section in Codex MCP
interface doc too.
2025-11-07 08:17:19 -08:00
Owen Lin
2030b28083 [app-server] feat: expose additional fields on Thread (#6338)
Add the following fields to Thread:

```
    pub preview: String,
    pub model_provider: String,
    pub created_at: i64,
```

Will prob need another PR once this lands:
https://github.com/openai/codex/pull/6337
2025-11-07 04:08:45 +00:00
Celia Chen
e84e39940b [App-server] Implement account/read endpoint (#6336)
This PR does two things:
1. add a new function in core that maps the core-internal plan type to
the external plan type;
2. implement account/read that get account status (v2 of
`getAuthStatus`).
2025-11-06 19:43:13 -08:00
pakrym-oai
e8905f6d20 Prefer wait_for_event over wait_for_event_with_timeout (#6349) 2025-11-06 18:11:11 -08:00
Shane Vitarana
316352be94 Fix apply_patch rename move path resolution (#5486)
Fixes https://github.com/openai/codex/issues/5485.

Fixed rename hunks so `apply_patch` resolves the destination path using
the verifier’s effective cwd, ensuring patches that run under `cd
<worktree> && apply_patch` stay inside the worktree.

Added a regression test
(`test_apply_patch_resolves_move_path_with_effective_cwd`) that
reproduced the old behavior (dest path resolved in the main repo) and
now passes.

Related to https://github.com/openai/codex/issues/5483.

Co-authored-by: Eric Traut <etraut@openai.com>
2025-11-06 17:02:09 -08:00
pakrym-oai
f8b30af6dc Prefer wait_for_event over wait_for_event_with_timeout. (#6346)
No need to specify the timeout in most cases.
2025-11-06 16:14:43 -08:00
Eric Traut
039a4b070e Updated the AI labeler rules to match the most recent issue tracker labels (#6347)
This PR updates the AI prompt used for the workflow that adds automated
labels to incoming issues. I've been updating and refining the list of
labels as I work through the issue backlog, and the old prompt was
becoming somewhat outdated.
2025-11-06 16:02:12 -08:00
pakrym-oai
c368c6aeea Remove shell tool when unified exec is enabled (#6345)
Also drop streameable shell that's just an alias for unified exec.
2025-11-06 15:46:24 -08:00
Eric Traut
0c647bc566 Don't retry "insufficient_quota" errors (#6340)
This PR makes an "insufficient quota" error fatal so we don't attempt to
retry it multiple times in the agent loop.

We have multiple bug reports from users about intermittent retry
behaviors, and this could explain some of them. With this change, we'll
eliminate the retries and surface a clear error message.

The PR is a nearly identical copy of [this
PR](https://github.com/openai/codex/pull/4837) contributed by
@abimaelmartell. The original PR has gone stale. Rather than wait for
the contributor to resolve merge conflicts, I wanted to get this change
in.
2025-11-06 15:12:01 -08:00
Ejaz Ahmed
e30f65118d feat: Enable CTRL-n and CTRL-p for navigating slash commands, files, history (#1994)
Adds CTRL-n and CTRL-p navigation for slash commands, files, and
history.
Closes #1992

Co-authored-by: Eric Traut <etraut@openai.com>
2025-11-06 14:58:18 -08:00
Jeremy Rose
1bd2d7a659 tui: fix backtracking past /status (#6335)
Fixes https://github.com/openai/codex/issues/4722

Supersedes https://github.com/openai/codex/pull/5058

Ideally we'd have a clearer way of separating history per-session than
by detecting a specific history cell type, but this is a fairly minimal
fix for now.
2025-11-06 14:50:07 -08:00
Gabriel Peal
65d53fd4b1 Make generate_ts prettier output warn-only (#6342)
Before, every file would be outputted with the time prettier spent
formatting it. This made downstream scripts way too noisy.
2025-11-06 17:45:51 -05:00
pakrym-oai
b5349202e9 Freeform unified exec output formatting (#6233) 2025-11-06 22:14:27 +00:00
Gabriel Peal
1b8cc8b625 [App Server] Add more session metadata to listConversations (#6337)
This unlocks a few new product experience for app server consumers
2025-11-06 17:13:24 -05:00
Jeremy Rose
8501b0b768 core: widen sandbox to allow certificate ops when network is enabled (#5980)
This allows `gh api` to work in the workspace-write sandbox w/ network
enabled. Without this we see e.g.

```
$ codex debug seatbelt --full-auto gh api repos/openai/codex/pulls --paginate -X GET -F state=all
Get "https://api.github.com/repos/openai/codex/pulls?per_page=100&state=all": tls: failed to verify certificate: x509: OSStatus -26276
```
2025-11-06 12:47:20 -08:00
Eric Traut
fe7eb18104 Updated contributing guidelines and PR template to request link to bug report in PR notes (#6332)
Some PRs are being submitted without reference to existing bug reports
or feature requests. This updates the PR template and contributing
guidelines to request that all PRs from the community contain such a
link. This provides additional context and helps prioritize, track, and
assess PRs.
2025-11-06 12:02:39 -08:00
Thibault Sottiaux
8c75ed39d5 feat: clarify that gpt-5-codex should not amend commits unless requested (#6333) 2025-11-06 11:42:47 -08:00
Owen Lin
fdb9fa301e chore: move relevant tests to app-server/tests/suite/v2 (#6289)
These are technically app-server v2 APIs, so move them to the same
directory as the others.
2025-11-06 10:53:17 -08:00
iceweasel-oai
871d442b8e Windows Sandbox: Show Everyone-writable directory warning (#6283)
Show a warning when Auto Sandbox mode becomes enabled, if we detect
Everyone-writable directories, since they cannot be protected by the
current implementation of the Sandbox.

This PR also includes changes to how we detect Everyone-writable to be
*much* faster
2025-11-06 10:44:42 -08:00
Ahmed Ibrahim
dbad5eeec6 chore: fix grammar mistakes (#6326) 2025-11-06 09:48:59 -08:00
vladislav doster
4b4252210b docs: Fix code fence and typo in advanced guide (#6295)
- add `bash` to code fence
- fix spelling of `JavaScript`
2025-11-06 09:00:28 -08:00
Owen Lin
6582554926 [app-server] feat: v2 Turn APIs (#6216)
Implements:
```
turn/start
turn/interrupt
```

along with their integration tests. These are relatively light wrappers
around the existing core logic, and changes to core logic are minimal.

However, an improvement made for developer ergonomics:
- `turn/start` replaces both `SendUserMessage` (no turn overrides) and
`SendUserTurn` (can override model, approval policy, etc.)
2025-11-06 16:36:36 +00:00
Thibault Sottiaux
649ce520c4 chore: rename for clarity (#6319)
Co-authored-by: Ahmed Ibrahim <aibrahim@openai.com>
2025-11-06 08:32:57 -08:00
Thibault Sottiaux
667e841d3e feat: support models with single reasoning effort (#6300) 2025-11-05 23:06:45 -08:00
Ahmed Ibrahim
63e1ef25af feat: add model nudge for queries (#6286) 2025-11-06 03:42:59 +00:00
Celia Chen
229d18f4d2 [App-server] Add account/login/cancel v2 endpoint (#6288)
Add `account/login/cancel` v2 endpoint for auth. this is similar
implementation to `cancelLoginChatgpt` v1 endpoint.
2025-11-06 01:13:55 +00:00
wizard
4a1a7f9685 fix: ToC so it doesn’t include itself or duplicate the end marker (#4388)
turns out the ToC was including itself when generating, which messed up
comparisons and sometimes made the file rewrite endlessly.

also fixed the slice so `<!-- End ToC -->` doesn’t get duplicated when
we insert the new ToC.

should behave nicely now - no extra rewrites, no doubled markers.

Co-authored-by: Eric Traut <etraut@openai.com>
2025-11-05 14:52:51 -08:00
Eric Traut
86c149ae8e Prevent dismissal of login menu in TUI (#6285)
We currently allow the user to dismiss the login menu via Ctrl+C. This
leaves them in a bad state where they're not auth'ed but have an input
prompt. In the extension, this isn't a problem because we don't allow
the user to dismiss the login screen.

Testing: I confirmed that Ctrl+C no longer dismisses the login menu.

This is an alternative (simpler) fix for a [community
PR](https://github.com/openai/codex/pull/3234).
2025-11-05 14:25:58 -08:00
Celia Chen
05f0b4f590 [App-server] Implement v2 for account/login/start and account/login/completed (#6183)
This PR implements `account/login/start` and `account/login/completed`.
Instead of having separate endpoints for login with chatgpt and api, we
have a single enum handling different login methods. For sync auth
methods like sign in with api key, we still send a `completed`
notification back to be compatible with the async login flow.
2025-11-05 13:52:50 -08:00
easong-openai
d4eda9d10b stop capturing r when environment selection modal is open (#6249)
This fixes an issue where you can't select environments with an r in them when the selection modal is open
2025-11-05 13:23:46 -08:00
Eric Traut
d7953aed74 Fixes intermittent test failures in CI (#6282)
I'm seeing two tests fail intermittently in CI. This PR attempts to
address (or at least mitigate) the flakiness.

* summarize_context_three_requests_and_instructions - The test snapshots
server.received_requests() immediately after observing TaskComplete.
Because the OpenAI /v1/responses call is streamed, the HTTP request can
still be draining when that event fires, so wiremock occasionally
reports only two captured requests. Fix is to wait for async activity to
complete.
* archive_conversation_moves_rollout_into_archived_directory - times out
on a slow CI run. Mitigation is to increase timeout value from 10s to
20s.
2025-11-05 13:12:25 -08:00
Owen Lin
2ab1650d4d [app-server] feat: v2 Thread APIs (#6214)
Implements:
```
thread/list
thread/start
thread/resume
thread/archive
```

along with their integration tests. These are relatively light wrappers
around the existing core logic, and changes to core logic are minimal.

However, an improvement made for developer ergonomics:
- `thread/start` and `thread/resume` automatically attaches a
conversation listener internally, so clients don't have to make a
separate `AddConversationListener` call like they do today.

For consistency, also updated `model/list` and `feedback/upload` (naming
conventions, list API params).
2025-11-05 20:28:43 +00:00
Gabriel Peal
79aa83ee39 Update rmcp to 0.8.5 (#6261)
Picks up https://github.com/modelcontextprotocol/rust-sdk/pull/511 which
should fix todoist and some other MCP server oauth and may further
resolve issues in https://github.com/openai/codex/issues/5045
2025-11-05 14:20:30 -05:00
Eric Traut
c4ebe4b078 Improved token refresh handling to address "Re-connecting" behavior (#6231)
Currently, when the access token expires, we attempt to use the refresh
token to acquire a new access token. This works most of the time.
However, there are situations where the refresh token is expired,
exhausted (already used to perform a refresh), or revoked. In those
cases, the current logic treats the error as transient and attempts to
retry it repeatedly.

This PR changes the token refresh logic to differentiate between
permanent and transient errors. It also changes callers to treat the
permanent errors as fatal rather than retrying them. And it provides
better error messages to users so they understand how to address the
problem. These error messages should also help us further understand why
we're seeing examples of refresh token exhaustion.

Here is the error message in the CLI. The same text appears within the
extension.

<img width="863" height="38" alt="image"
src="https://github.com/user-attachments/assets/7ffc0d08-ebf0-4900-b9a9-265064202f4f"
/>

I also correct the spelling of "Re-connecting", which shouldn't have a
hyphen in it.

Testing: I manually tested these code paths by adding temporary code to
programmatically cause my refresh token to be exhausted (by calling the
token refresh endpoint in a tight loop more than 50 times). I then
simulated an access token expiration, which caused the token refresh
logic to be invoked. I confirmed that the updated logic properly handled
the error condition.

Note: We earlier discussed the idea of forcefully logging out the user
at the point where token refresh failed. I made several attempts to do
this, and all of them resulted in a bad UX. It's important to surface
this error to users in a way that explains the problem and tells them
that they need to log in again. We also previously discussed deleting
the auth.json file when this condition is detected. That also creates
problems because it effectively changes the auth status from logged in
to logged out, and this causes odd failures and inconsistent UX. I think
it's therefore better not to delete auth.json in this case. If the user
closes the CLI or VSCE and starts it again, we properly detect that the
access token is expired and the refresh token is "dead", and we force
the user to go through the login flow at that time.

This should address aspects of #6191, #5679, and #5505
2025-11-05 10:51:57 -08:00
Ahmed Ibrahim
1a89f70015 refactor Conversation history file into its own directory (#6229)
This is just a refactor of `conversation_history` file by breaking it up
into multiple smaller ones with helper. This refactor will help us move
more functionality related to context management here. in a clean way.
2025-11-05 10:49:35 -08:00
Jeremy Rose
62474a30e8 tui: refactor ChatWidget and BottomPane to use Renderables (#5565)
- introduce RenderableItem to support both owned and borrowed children
in composite Renderables
- refactor some of our gnarlier manual layouts, BottomPane and
ChatWidget, to use ColumnRenderable
- Renderable and friends now handle cursor_pos()
2025-11-05 09:50:40 -08:00
Dan Hernandez
9a10e80ab7 Add modelReasoningEffort option to TypeScript SDK (#6237)
## Summary
- Adds `ModelReasoningEffort` type to TypeScript SDK with values:
`minimal`, `low`, `medium`, `high`
- Adds `modelReasoningEffort` option to `ThreadOptions`
- Forwards the option to the codex CLI via `--config
model_reasoning_effort="<value>"`
- Includes test coverage for the new option

## Changes
- `sdk/typescript/src/threadOptions.ts`: Define `ModelReasoningEffort`
type and add to `ThreadOptions`
- `sdk/typescript/src/index.ts`: Export `ModelReasoningEffort` type
- `sdk/typescript/src/exec.ts`: Forward `modelReasoningEffort` to CLI as
config flag
- `sdk/typescript/src/thread.ts`: Pass option through to exec (+ debug
logging)
- `sdk/typescript/tests/run.test.ts`: Add test for
`modelReasoningEffort` flag forwarding

---------

Co-authored-by: Eric Traut <etraut@openai.com>
2025-11-05 08:51:03 -08:00
Gabriel Peal
9b538a8672 Upgrade rmcp to 0.8.4 (#6234)
Picks up https://github.com/modelcontextprotocol/rust-sdk/pull/509 which
fixes https://github.com/openai/codex/issues/6164
2025-11-05 00:23:24 -05:00
Andrew Dirksen
95af417923 allow codex to be run from pid 1 (#4200)
Previously it was not possible for codex to run commands as the init
process (pid 1) in linux. Commands run in containers tend to see their
own pid as 1. See https://github.com/openai/codex/issues/4198

This pr implements the solution mentioned in that issue.

Co-authored-by: Eric Traut <etraut@openai.com>
2025-11-04 17:54:46 -08:00
Soroush Yousefpour
fff576cf98 fix(core): load custom prompts from symlinked Markdown files (#3643)
- Discover prompts via fs::metadata to follow symlinks

- Add Unix-only symlink test in custom_prompts.rs

- Update docs/prompts.md to mention symlinks

Fixes #3637

---------

Signed-off-by: Soroush Yousefpour <h.yusefpour@gmail.com>
Co-authored-by: dedrisian-oai <dedrisian@openai.com>
Co-authored-by: Eric Traut <etraut@openai.com>
2025-11-04 17:44:02 -08:00
Lukas
1575f0504c Fix nix build (#6230)
Previously, the `nix build .#default` command fails due to a missing
output hash in the `./codex-rs/default.nix` for `crossterm-0.28.1`:

```
error: No hash was found while vendoring the git dependency crossterm-0.28.1. You can add
a hash through the `outputHashes` argument of `importCargoLock`:

outputHashes = {
 "crossterm-0.28.1" = "<hash>";
};

If you use `buildRustPackage`, you can add this attribute to the `cargoLock`
attribute set.
```

This PR adds the missing hash:

```diff
cargoLock.outputHashes = {
  "ratatui-0.29.0" = "sha256-HBvT5c8GsiCxMffNjJGLmHnvG77A6cqEL+1ARurBXho=";
+ "crossterm-0.28.1" = "sha256-6qCtfSMuXACKFb9ATID39XyFDIEMFDmbx6SSmNe+728=";
};
```

With this change, `nix build .#default` succeeds:

```
> nix build .#default --max-jobs 1 --cores 2

warning: Git tree '/home/lukas/r/github.com/lukasl-dev/codex' is dirty
[1/0/1 built] building codex-rs-0.1.0 (buildPhase)[1/0/1 built] building codex-rs-0.1.0 (buildP[1/0/1 built] building codex-rs-0.1.0 (buildPhase):    [1/0/1 built] building codex-rs-0.1.0 (b[1/0/1 built] building codex-rs-0.1.0 (buildPhase):    Compi[1/0/1 built] building codex-rs-0.1

> ./result/bin/codex
  You are running Codex in /home/lukas/r/github.com/lukasl-dev/codex

  Since this folder is version controlled, you may wish to allow Codex to work in this folder without asking for approval.
  ...
```
2025-11-04 17:07:37 -08:00
Owen Lin
edf4c3f627 [app-server] feat: export.rs supports a v2 namespace, initial v2 notifications (#6212)
**Typescript and JSON schema exports**
While working on Thread/Turn/Items type definitions, I realize we will
run into name conflicts between v1 and v2 APIs (e.g. `RateLimitWindow`
which won't be reusable since v1 uses `RateLimitWindow` from `protocol/`
which uses snake_case, but we want to expose camelCase everywhere, so
we'll define a V2 version of that struct that serializes as camelCase).

To set us up for a clean and isolated v2 API, generate types into a
`v2/` namespace for both typescript and JSON schema.
- TypeScript: v2 types emit under `out_dir/v2/*.ts`, and root index.ts
now re-exports them via `export * as v2 from "./v2"`;.
- JSON Schemas: v2 definitions bundle under `#/definitions/v2/*` rather
than the root.

The location for the original types (v1 and types pulled from
`protocol/` and other core crates) haven't changed and are still at the
root. This is for backwards compatibility: no breaking changes to
existing usages of v1 APIs and types.

**Notifications**
While working on export.rs, I:
- refactored server/client notifications with macros (like we already do
for methods) so they also get exported (I noticed they weren't being
exported at all).
- removed the hardcoded list of types to export as JSON schema by
leveraging the existing macros instead
- and took a stab at API V2 notifications. These aren't wired up yet,
and I expect to iterate on these this week.
2025-11-05 01:02:39 +00:00
Ahmed Ibrahim
d40a6b7f73 fix: Update the deprecation message to link to the docs (#6211)
The deprecation message is currently a bit confusing. Users may not
understand what is `[features].x`. I updated the docs and the
deprecation message for more guidance.

---------

Co-authored-by: Gabriel Peal <gpeal@users.noreply.github.com>
2025-11-04 21:02:27 +00:00
Dylan Hurd
3a22018edd Revert "fix: pin musl 1.2.5 for DNS fixes" (#6222)
Reverts openai/codex#6189
2025-11-04 11:56:40 -08:00
Ahmed Ibrahim
fe54c216a3 ignore deltas in codex_delegate (#6208)
ignore legacy deltas in codex-delegate to avoid this
[issue](https://github.com/openai/codex/pull/6202).
2025-11-04 19:21:35 +00:00
Dylan Hurd
cb6584de46 fix: pin musl 1.2.5 for DNS fixes (#6189)
## Summary
musl 1.2.5 includes [several fixes to DNS over
TCP](https://www.openwall.com/lists/musl/2024/03/01/2), which appears to
be the root cause of #6116.

This approach is a bit janky, but according to codex:
> On the Ubuntu 24.04 runners we use, apt-cache policy musl-tools shows
only the distro build (1.2.4-2ubuntu2)"

We should build with this version and confirm.

## Testing
- [ ] TODO: test and see if this fixes Azure issues
2025-11-04 09:17:16 -08:00
Ahmed Ibrahim
7e068e1094 fix: ignore reasoning deltas because we send it with turn item (#6202)
should fix this:

<img width="2418" height="242" alt="image"
src="https://github.com/user-attachments/assets/f818d00b-ed3a-479b-94a7-e4bc5db6326e"
/>
2025-11-04 08:27:16 -08:00
Celia Chen
d3187dbc17 [App-server] v2 for account/updated and account/logout (#6175)
V2 for `account/updated` and `account/logout` for app server. correspond
to old `authStatusChange` and `LogoutChatGpt` respectively. Followup PRs
will make other v2 endpoints call `account/updated` instead of
`authStatusChange` too.
2025-11-03 22:01:33 -08:00
Robby He
dc2f26f7b5 Fix is_api_message to correctly exclude reasoning messages (#6156)
## Problem

The `is_api_message` function in `conversation_history.rs` had a
misalignment between its documentation and implementation:

- **Comment stated**: "Anything that is not a system message or
'reasoning' message is considered an API message"
- **Code behavior**: Was returning `true` for `ResponseItem::Reasoning`,
meaning reasoning messages were incorrectly treated as API messages

This inconsistency could lead to reasoning messages being persisted in
conversation history when they should be filtered out.

## Root Cause

Investigation revealed that reasoning messages are explicitly excluded
throughout the codebase:

1. **Chat completions API** (lines 267-272 in `chat_completions.rs`)
omits reasoning from conversation history:
   ```rust
   ResponseItem::Reasoning { .. } | ResponseItem::Other => {
       // Omit these items from the conversation history.
       continue;
   }
   ```

2. **Existing tests** like `drops_reasoning_when_last_role_is_user` and
`ignores_reasoning_before_last_user` validate that reasoning should be
excluded from API payloads

## Solution

Fixed the `is_api_message` function to align with its documentation and
the rest of the codebase:

```rust
// Before: Reasoning was incorrectly returning true
ResponseItem::Reasoning { .. } | ResponseItem::WebSearchCall { .. } => true,

// After: Reasoning correctly returns false  
ResponseItem::WebSearchCall { .. } => true,
ResponseItem::Reasoning { .. } | ResponseItem::Other => false,
```

## Testing

- Enhanced existing test to verify reasoning messages are properly
filtered out
- All 264 core tests pass, including 8 chat completions tests that
validate reasoning behavior
- No regressions introduced

This ensures reasoning messages are consistently excluded from API
message processing across the entire codebase.
2025-11-03 20:55:41 -08:00
Ricardo Ander-Egg
553db8def1 Follow symlinks during file search (#4453)
I have read the CLA Document and I hereby sign the CLA

Closes #4452

This fixes a usability issue where users with symlinked folders in their
working directory couldn't search those files using the `@` file search
feature.

## Rationale

The "bug" was in the file search implementation in
`codex-rs/file-search/src/lib.rs`. The `WalkBuilder` was using default
settings which don't follow symlinks, causing two related issues:

1. Partial search results: The `@` search would find symlinked
directories but couldn't find files inside them
2. Inconsistent behavior: Users expect symlinked folders to behave like
regular folders in search results.

## Root cause

The `ignore` crate's `WalkBuilder` defaults to `.follow_links(false)`
[[source](9802945e63/crates/ignore/src/walk.rs (L532))],
so when traversing the file system, it would:

- Detect symlinked directories as directory entries
- But not traverse into them to index their contents
- The `get_file_path` function would then filter out actual directories,
leaving only the symlinked folder itself as a result

Fix: Added `.follow_links(true)` to the `WalkBuilder` configuration,
making the file search follow symlinks and index their contents just
like regular directories.

This change maintains backward compatibility since symlink following is
generally expected behavior for file search tools, and it aligns with
how users expect the `@` search feature to work.

Co-authored-by: Eric Traut <etraut@openai.com>
2025-11-03 20:28:33 -08:00
Lucas Freire Sangoi
ab63a47173 docs: add example config.toml (#5175)
I was missing an example config.toml, and following docs/config.md alone
was slower. I had GPT-5 scan the codebase for every accepted config key,
check the defaults, and generate a single example config.toml with
annotations. It lists all keys Codex reads from TOML, sets each to its
effective default where it exists, leaves optional ones commented, and
adds short comments on purpose and valid values. This should make
onboarding faster and reduce configuration errors. I can rename it to
config.example.toml or move it under docs/ if you prefer.
2025-11-03 18:19:26 -08:00
Ahmed Ibrahim
e658c6c73b fix: --search shouldn't show deprecation message (#6180)
Use the new feature flags instead of the old config.
2025-11-04 00:11:50 +00:00
Eric Traut
1e0e553304 Fixed notify handler so it passes correct input_messages details (#6143)
This fixes bug #6121. 

The `input_messages` field passed to the notify handler is currently
empty because the logic is incorrectly including the OutputText rather
than InputText. I've fixed that and added proper filtering to remove
messages associated with AGENTS.md and other context injected by the
harness.

Testing: I wrote a notify handler and verified that the user prompt is
correctly passed through to the handler.
2025-11-03 14:23:04 -08:00
iceweasel-oai
07b7d28937 log sandbox commands to $CODEX_HOME instead of cwd (#6171)
Logging commands in the Windows Sandbox is temporary, but while we are
doing it, let's always write to CODEX_HOME instead of dirtying the cwd.
2025-11-03 13:12:33 -08:00
Ahmed Ibrahim
6ee7fbcfff feat: add the time after aborting (#5996)
Tell the model how much time passed after the user aborted the call.
2025-11-03 11:44:06 -08:00
Jeremy Rose
5f3a0473f1 tui: refine text area word separator handling (#5541)
## Summary
- replace the word part enum with a simple `is_word_separator` helper
- keep word-boundary logic aligned with the helper and punctuation-aware
behavior
- extend forward/backward deletion tests to cover whitespace around
separators

## Testing
- just fix -p codex-tui
- cargo test -p codex-tui


------
https://chatgpt.com/codex/tasks/task_i_68f91c71d838832ca2a3c4f0ec1b55d4
2025-11-03 11:33:34 -08:00
iceweasel-oai
2eda75a8ee Do not skip trust prompt on Windows if sandbox is enabled. (#6167)
If the experimental windows sandbox is enabled, the trust prompt should
show on Windows.
2025-11-03 11:27:45 -08:00
Michael Bolin
e1f098b9b7 feat: add options to responses-api-proxy to support Azure (#6129)
This PR introduces an `--upstream-url` option to the proxy CLI that
determines the URL that Responses API requests should be forwarded to.
To preserve existing behavior, the default value is
`"https://api.openai.com/v1/responses"`.

The motivation for this change is that the [Codex GitHub
Action](https://github.com/openai/codex-action) should support those who
use the OpenAI Responses API via Azure. Relevant issues:

- https://github.com/openai/codex-action/issues/28
- https://github.com/openai/codex-action/issues/38
- https://github.com/openai/codex-action/pull/44

Though rather than introduce a bunch of new Azure-specific logic in the
action as https://github.com/openai/codex-action/pull/44 proposes, we
should leverage our Responses API proxy to get the _hardening_ benefits
it provides:


d5853d9c47/codex-rs/responses-api-proxy/README.md (hardening-details)

This PR should make this straightforward to incorporate in the action.
To see how the updated version of the action would consume these new
options, see https://github.com/openai/codex-action/pull/47.
2025-11-03 10:06:00 -08:00
pakrym-oai
e5e13479d0 Include reasoning tokens in the context window calculation (#6161)
This value is used to determine whether mid-turn compaction is required.
Reasoning items are only excluded between turns (and soon will start to
be preserved even across turns) so it's incorrect to subtract
reasoning_output_tokens mid term.

This will result in higher values reported between turns but we are also
looking into preserving reasoning items for the entire conversation to
improve performance and caching.
2025-11-03 10:02:23 -08:00
Jeremy Rose
7bc3ca9e40 Fix rmcp client feature flag reference (#6051)
## Summary
- update the OAuth login error message to reference
`[features].rmcp_client` in config

## Testing
- cargo test -p codex-cli

------
https://chatgpt.com/codex/tasks/task_i_69050365dc84832ca298f863c879a59a
2025-11-03 09:59:19 -08:00
Mark Hemmings
4d8b71d412 Fix typo in error message for OAuth login (#6159)
Error message for attempting to OAuth with a remote RCP is incorrect and
misleading. The correct config is

```
[features]
rmcp_client = true
```

Co-authored-by: Eric Traut <etraut@openai.com>
2025-11-03 08:59:00 -08:00
pygarap
b484672961 Add documentation for slash commands in docs/slash_commands.md. (#5685)
This pull request adds a new documentation section to explain the
available slash commands in Codex. The update introduces a clear
overview and a reference table for built-in commands, making it easier
for users to understand and utilize these features.

Documentation updates:

* Added a new section to `docs/slash_commands.md` describing what slash
commands are and listing all built-in commands with their purposes in a
formatted table.
2025-11-03 08:27:13 -08:00
Vinh Nguyen
a1ee10b438 fix: improve usage URLs in status card and snapshots (#6111)
Hi OpenAI Codex team, currently "Visit chatgpt.com/codex/settings/usage
for up-to-date information on rate limits and credits" message in status
card and error messages. For now, without the "https://" prefix, the
link cannot be clicked directly from most terminals or chat interfaces.

<img width="636" height="127" alt="Screenshot 2025-11-02 at 22 47 06"
src="https://github.com/user-attachments/assets/5ea11e8b-fb74-451c-85dc-f4d492b2678b"
/>

---

The fix is intent to improve this issue:

- It makes the link clickable in terminals that support it, hence better
accessibility
- It follows standard URL formatting practices
- It maintains consistency with other links in the application (like the
existing "https://openai.com/chatgpt/pricing" links)

Thank you!
2025-11-02 21:44:59 -08:00
Eric Traut
dccce34d84 Fix "archive conversation" on Windows (#6124)
Addresses issue https://github.com/openai/codex/issues/3582 where an
"archive conversation" command in the extension fails on Windows.

The problem is that the `archive_conversation` api server call is not
canonicalizing the path to the rollout path when performing its check to
verify that the rollout path is in the sessions directory. This causes
it to fail 100% of the time on Windows.

Testing: I was able to repro the error on Windows 100% prior to this
change. After the change, I'm no longer able to repro.
2025-11-02 21:41:05 -08:00
dependabot[bot]
f5945d7c03 chore(deps): bump actions/upload-artifact from 4 to 5 (#6137)
Bumps
[actions/upload-artifact](https://github.com/actions/upload-artifact)
from 4 to 5.
<details>
<summary>Release notes</summary>
<p><em>Sourced from <a
href="https://github.com/actions/upload-artifact/releases">actions/upload-artifact's
releases</a>.</em></p>
<blockquote>
<h2>v5.0.0</h2>
<h2>What's Changed</h2>
<p><strong>BREAKING CHANGE:</strong> this update supports Node
<code>v24.x</code>. This is not a breaking change per-se but we're
treating it as such.</p>
<ul>
<li>Update README.md by <a
href="https://github.com/GhadimiR"><code>@​GhadimiR</code></a> in <a
href="https://redirect.github.com/actions/upload-artifact/pull/681">actions/upload-artifact#681</a></li>
<li>Update README.md by <a
href="https://github.com/nebuk89"><code>@​nebuk89</code></a> in <a
href="https://redirect.github.com/actions/upload-artifact/pull/712">actions/upload-artifact#712</a></li>
<li>Readme: spell out the first use of GHES by <a
href="https://github.com/danwkennedy"><code>@​danwkennedy</code></a> in
<a
href="https://redirect.github.com/actions/upload-artifact/pull/727">actions/upload-artifact#727</a></li>
<li>Update GHES guidance to include reference to Node 20 version by <a
href="https://github.com/patrikpolyak"><code>@​patrikpolyak</code></a>
in <a
href="https://redirect.github.com/actions/upload-artifact/pull/725">actions/upload-artifact#725</a></li>
<li>Bump <code>@actions/artifact</code> to <code>v4.0.0</code></li>
<li>Prepare <code>v5.0.0</code> by <a
href="https://github.com/danwkennedy"><code>@​danwkennedy</code></a> in
<a
href="https://redirect.github.com/actions/upload-artifact/pull/734">actions/upload-artifact#734</a></li>
</ul>
<h2>New Contributors</h2>
<ul>
<li><a href="https://github.com/GhadimiR"><code>@​GhadimiR</code></a>
made their first contribution in <a
href="https://redirect.github.com/actions/upload-artifact/pull/681">actions/upload-artifact#681</a></li>
<li><a href="https://github.com/nebuk89"><code>@​nebuk89</code></a> made
their first contribution in <a
href="https://redirect.github.com/actions/upload-artifact/pull/712">actions/upload-artifact#712</a></li>
<li><a
href="https://github.com/danwkennedy"><code>@​danwkennedy</code></a>
made their first contribution in <a
href="https://redirect.github.com/actions/upload-artifact/pull/727">actions/upload-artifact#727</a></li>
<li><a
href="https://github.com/patrikpolyak"><code>@​patrikpolyak</code></a>
made their first contribution in <a
href="https://redirect.github.com/actions/upload-artifact/pull/725">actions/upload-artifact#725</a></li>
</ul>
<p><strong>Full Changelog</strong>: <a
href="https://github.com/actions/upload-artifact/compare/v4...v5.0.0">https://github.com/actions/upload-artifact/compare/v4...v5.0.0</a></p>
<h2>v4.6.2</h2>
<h2>What's Changed</h2>
<ul>
<li>Update to use artifact 2.3.2 package &amp; prepare for new
upload-artifact release by <a
href="https://github.com/salmanmkc"><code>@​salmanmkc</code></a> in <a
href="https://redirect.github.com/actions/upload-artifact/pull/685">actions/upload-artifact#685</a></li>
</ul>
<h2>New Contributors</h2>
<ul>
<li><a href="https://github.com/salmanmkc"><code>@​salmanmkc</code></a>
made their first contribution in <a
href="https://redirect.github.com/actions/upload-artifact/pull/685">actions/upload-artifact#685</a></li>
</ul>
<p><strong>Full Changelog</strong>: <a
href="https://github.com/actions/upload-artifact/compare/v4...v4.6.2">https://github.com/actions/upload-artifact/compare/v4...v4.6.2</a></p>
<h2>v4.6.1</h2>
<h2>What's Changed</h2>
<ul>
<li>Update to use artifact 2.2.2 package by <a
href="https://github.com/yacaovsnc"><code>@​yacaovsnc</code></a> in <a
href="https://redirect.github.com/actions/upload-artifact/pull/673">actions/upload-artifact#673</a></li>
</ul>
<p><strong>Full Changelog</strong>: <a
href="https://github.com/actions/upload-artifact/compare/v4...v4.6.1">https://github.com/actions/upload-artifact/compare/v4...v4.6.1</a></p>
<h2>v4.6.0</h2>
<h2>What's Changed</h2>
<ul>
<li>Expose env vars to control concurrency and timeout by <a
href="https://github.com/yacaovsnc"><code>@​yacaovsnc</code></a> in <a
href="https://redirect.github.com/actions/upload-artifact/pull/662">actions/upload-artifact#662</a></li>
</ul>
<p><strong>Full Changelog</strong>: <a
href="https://github.com/actions/upload-artifact/compare/v4...v4.6.0">https://github.com/actions/upload-artifact/compare/v4...v4.6.0</a></p>
<h2>v4.5.0</h2>
<h2>What's Changed</h2>
<ul>
<li>fix: deprecated <code>Node.js</code> version in action by <a
href="https://github.com/hamirmahal"><code>@​hamirmahal</code></a> in <a
href="https://redirect.github.com/actions/upload-artifact/pull/578">actions/upload-artifact#578</a></li>
<li>Add new <code>artifact-digest</code> output by <a
href="https://github.com/bdehamer"><code>@​bdehamer</code></a> in <a
href="https://redirect.github.com/actions/upload-artifact/pull/656">actions/upload-artifact#656</a></li>
</ul>
<h2>New Contributors</h2>
<ul>
<li><a
href="https://github.com/hamirmahal"><code>@​hamirmahal</code></a> made
their first contribution in <a
href="https://redirect.github.com/actions/upload-artifact/pull/578">actions/upload-artifact#578</a></li>
</ul>
<!-- raw HTML omitted -->
</blockquote>
<p>... (truncated)</p>
</details>
<details>
<summary>Commits</summary>
<ul>
<li><a
href="330a01c490"><code>330a01c</code></a>
Merge pull request <a
href="https://redirect.github.com/actions/upload-artifact/issues/734">#734</a>
from actions/danwkennedy/prepare-5.0.0</li>
<li><a
href="03f2824452"><code>03f2824</code></a>
Update <code>github.dep.yml</code></li>
<li><a
href="905a1ecb59"><code>905a1ec</code></a>
Prepare <code>v5.0.0</code></li>
<li><a
href="2d9f9cdfa9"><code>2d9f9cd</code></a>
Merge pull request <a
href="https://redirect.github.com/actions/upload-artifact/issues/725">#725</a>
from patrikpolyak/patch-1</li>
<li><a
href="9687587dec"><code>9687587</code></a>
Merge branch 'main' into patch-1</li>
<li><a
href="2848b2cda0"><code>2848b2c</code></a>
Merge pull request <a
href="https://redirect.github.com/actions/upload-artifact/issues/727">#727</a>
from danwkennedy/patch-1</li>
<li><a
href="9b511775fd"><code>9b51177</code></a>
Spell out the first use of GHES</li>
<li><a
href="cd231ca1ed"><code>cd231ca</code></a>
Update GHES guidance to include reference to Node 20 version</li>
<li><a
href="de65e23aa2"><code>de65e23</code></a>
Merge pull request <a
href="https://redirect.github.com/actions/upload-artifact/issues/712">#712</a>
from actions/nebuk89-patch-1</li>
<li><a
href="8747d8cd76"><code>8747d8c</code></a>
Update README.md</li>
<li>Additional commits viewable in <a
href="https://github.com/actions/upload-artifact/compare/v4...v5">compare
view</a></li>
</ul>
</details>
<br />


[![Dependabot compatibility
score](https://dependabot-badges.githubapp.com/badges/compatibility_score?dependency-name=actions/upload-artifact&package-manager=github_actions&previous-version=4&new-version=5)](https://docs.github.com/en/github/managing-security-vulnerabilities/about-dependabot-security-updates#about-compatibility-scores)

Dependabot will resolve any conflicts with this PR as long as you don't
alter it yourself. You can also trigger a rebase manually by commenting
`@dependabot rebase`.

[//]: # (dependabot-automerge-start)
[//]: # (dependabot-automerge-end)

---

<details>
<summary>Dependabot commands and options</summary>
<br />

You can trigger Dependabot actions by commenting on this PR:
- `@dependabot rebase` will rebase this PR
- `@dependabot recreate` will recreate this PR, overwriting any edits
that have been made to it
- `@dependabot merge` will merge this PR after your CI passes on it
- `@dependabot squash and merge` will squash and merge this PR after
your CI passes on it
- `@dependabot cancel merge` will cancel a previously requested merge
and block automerging
- `@dependabot reopen` will reopen this PR if it is closed
- `@dependabot close` will close this PR and stop Dependabot recreating
it. You can achieve the same result by closing it manually
- `@dependabot show <dependency name> ignore conditions` will show all
of the ignore conditions of the specified dependency
- `@dependabot ignore this major version` will close this PR and stop
Dependabot creating any more for this major version (unless you reopen
the PR or upgrade to it yourself)
- `@dependabot ignore this minor version` will close this PR and stop
Dependabot creating any more for this minor version (unless you reopen
the PR or upgrade to it yourself)
- `@dependabot ignore this dependency` will close this PR and stop
Dependabot creating any more for this dependency (unless you reopen the
PR or upgrade to it yourself)


</details>

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-11-02 20:38:45 -08:00
Rohan Godha
5fcf923c19 fix: pasting api key stray character (#4903)
When signing in with an API key, pasting (with command+v on mac) adds a
stray `v` character to the end of the api key.



demo video (where I'm pasting in `sk-something-super-secret`)


https://github.com/user-attachments/assets/b2b34b5f-c7e4-4760-9657-c35686dd8bb8
2025-11-03 04:19:08 +00:00
Eric Traut
0c7efa0cfd Fix incorrect "deprecated" message about experimental config key (#6131)
When I enable `experimental_sandbox_command_assessment`, I get an
incorrect deprecation warning: "experimental_sandbox_command_assessment
is deprecated. Use experimental_sandbox_command_assessment instead."

This PR fixes this error.
2025-11-02 16:33:09 -08:00
Eric Traut
d5853d9c47 Changes to sandbox command assessment feature based on initial experiment feedback (#6091)
* Removed sandbox risk categories; feedback indicates that these are not
that useful and "less is more"
* Tweaked the assessment prompt to generate terser answers
* Fixed bug in orchestrator that prevents this feature from being
exposed in the extension
2025-11-01 14:52:23 -07:00
Thomas Stokes
d9118c04bf Parse the Azure OpenAI rate limit message (#5956)
Fixes #4161

Currently Codex uses a regex to parse the "Please try again in 1.898s"
OpenAI-style rate limit message, so that it can wait the correct
duration before retrying. Azure OpenAI returns a different error that
looks like "Rate limit exceeded. Try again in 35 seconds."

This PR extends the regex and parsing code to match in a more fuzzy
manner, handling anything matching the pattern "try again in
\<duration>\<unit>".
2025-11-01 09:33:13 -07:00
Vinh Nguyen
91e65ac0ce docs: Fix link anchor and markdown format in advanced guide (#5649)
Hi OpenAI Codex team, this PR fix an rendering issue in the Markdown
table and anchor link in the
[docs/advanced.md](https://github.com/openai/codex/blob/main/docs/advanced.md).

Thank you!

Co-authored-by: Eric Traut <etraut@openai.com>
2025-10-31 16:53:09 -07:00
Tony Dong
1ac4fb45d2 Fixing small typo in docs (#5659)
Fixing a typo in the docs

Co-authored-by: Eric Traut <etraut@openai.com>
2025-10-31 16:41:05 -07:00
Jeremy Rose
07b8bdfbf1 tui: patch crossterm for better color queries (#5935)
See
https://github.com/crossterm-rs/crossterm/compare/master...nornagon:crossterm:nornagon/color-query

This patches crossterm to add support for querying fg/bg color as part
of the crossterm event loop, which fixes some issues where this query
would fight with other input.

- dragging screenshots into the cli would sometimes paste half of the
pathname instead of being recognized as an image
(https://github.com/openai/codex/issues/5603)
- Fixes https://github.com/openai/codex/issues/4945
2025-10-31 16:36:41 -07:00
Anton Panasenko
0f22067242 [codex][app-server] improve error response for client requests (#6050) 2025-10-31 15:28:04 -07:00
Ritesh Chauhan
d7f8b97541 docs: fix broken link in contributing guide (#4973)
## Summary

This PR fixes a broken self-referencing link in the contributing
documentation.

## Changes

- Removed the phrase 'Following the [development
setup](#development-workflow) instructions above' from the Development
workflow section
- The link referenced a non-existent section and the phrase didn't make
logical sense in context

## Before

The text referenced 'development setup instructions above' but:
1. No section called 'development setup' exists
2. There were no instructions 'above' that point
3. The link pointed to the same section it was in

## After

Simplified to: 'Ensure your change is free of lint warnings and test
failures.'

## Type

Documentation fix


I have read the CLA Document and I hereby sign the CLA

Co-authored-by: Ritesh Chauhan <sagar.chauhn11@gmail.com>
2025-10-31 15:09:35 -07:00
jif-oai
611e00c862 feat: compactor 2 (#6027)
Co-authored-by: pakrym-oai <pakrym@openai.com>
2025-10-31 14:27:08 -07:00
Ahmed Ibrahim
c8ebb2a0dc Add warning on compact (#6052)
This PR introduces the ability for `core` to send `warnings` as it can
send `errors. It also sends a warning on compaction.

<img width="811" height="187" alt="image"
src="https://github.com/user-attachments/assets/0947a42d-b720-420d-b7fd-115f8a65a46a"
/>
2025-10-31 13:27:33 -07:00
Dylan Hurd
88e083a9d0 chore: Add shell serialization tests for json (#6043)
## Summary
Can never have enough tests on this code path - checking that json
inside a shell call is deserialized correctly.

## Tests
- [x] These are tests 😎
2025-10-31 11:01:58 -07:00
Ahmed Ibrahim
1c8507b32a Truncate total tool calls text (#5979)
Put a cap on the aggregate output of text content on tool calls.

---------

Co-authored-by: Gabriel Peal <gpeal@users.noreply.github.com>
2025-10-31 10:30:36 -07:00
uonr
23f31c6bff docs: "Configuration" is not belongs "Getting started" (#4797)
I finished reading “Getting Started,” but couldn’t find the
“Configuration” section in the README. After following the link, I
realized “Configuration” is in a separate file, so I updated the README
accordingly.

# External (non-OpenAI) Pull Request Requirements

Before opening this Pull Request, please read the dedicated
"Contributing" markdown file or your PR may be closed:
https://github.com/openai/codex/blob/main/docs/contributing.md

If your PR conforms to our contribution guidelines, replace this text
with a detailed and high quality description of your changes.

Co-authored-by: Eric Traut <etraut@openai.com>
2025-10-31 10:17:42 -07:00
dependabot[bot]
ff48ae192b chore(deps): bump indexmap from 2.10.0 to 2.11.4 in /codex-rs (#4804)
Bumps [indexmap](https://github.com/indexmap-rs/indexmap) from 2.10.0 to
2.11.4.
<details>
<summary>Changelog</summary>
<p><em>Sourced from <a
href="https://github.com/indexmap-rs/indexmap/blob/main/RELEASES.md">indexmap's
changelog</a>.</em></p>
<blockquote>
<h2>2.11.4 (2025-09-18)</h2>
<ul>
<li>Updated the <code>hashbrown</code> dependency to a range allowing
0.15 or 0.16.</li>
</ul>
<h2>2.11.3 (2025-09-15)</h2>
<ul>
<li>Make the minimum <code>serde</code> version only apply when
&quot;serde&quot; is enabled.</li>
</ul>
<h2>2.11.2 (2025-09-15)</h2>
<ul>
<li>Switched the &quot;serde&quot; feature to depend on
<code>serde_core</code>, improving build
parallelism in cases where other dependents have enabled
&quot;serde/derive&quot;.</li>
</ul>
<h2>2.11.1 (2025-09-08)</h2>
<ul>
<li>Added a <code>get_key_value_mut</code> method to
<code>IndexMap</code>.</li>
<li>Removed the unnecessary <code>Ord</code> bound on
<code>insert_sorted_by</code> methods.</li>
</ul>
<h2>2.11.0 (2025-08-22)</h2>
<ul>
<li>Added <code>insert_sorted_by</code> and
<code>insert_sorted_by_key</code> methods to <code>IndexMap</code>,
<code>IndexSet</code>, and <code>VacantEntry</code>, like customizable
versions of <code>insert_sorted</code>.</li>
<li>Added <code>is_sorted</code>, <code>is_sorted_by</code>, and
<code>is_sorted_by_key</code> methods to
<code>IndexMap</code> and <code>IndexSet</code>, as well as their
<code>Slice</code> counterparts.</li>
<li>Added <code>sort_by_key</code> and <code>sort_unstable_by_key</code>
methods to <code>IndexMap</code> and
<code>IndexSet</code>, as well as parallel counterparts.</li>
<li>Added <code>replace_index</code> methods to <code>IndexMap</code>,
<code>IndexSet</code>, and <code>VacantEntry</code>
to replace the key (or set value) at a given index.</li>
<li>Added optional <code>sval</code> serialization support.</li>
</ul>
</blockquote>
</details>
<details>
<summary>Commits</summary>
<ul>
<li><a
href="03f9e58626"><code>03f9e58</code></a>
Merge pull request <a
href="https://redirect.github.com/indexmap-rs/indexmap/issues/418">#418</a>
from a1phyr/hashbrown_0.16</li>
<li><a
href="ee6080d480"><code>ee6080d</code></a>
Release 2.11.4</li>
<li><a
href="a7da8f181e"><code>a7da8f1</code></a>
Use a range for hashbrown</li>
<li><a
href="0cd5aefb44"><code>0cd5aef</code></a>
Update <code>hashbrown</code> to 0.16</li>
<li><a
href="fd5c819daf"><code>fd5c819</code></a>
Merge pull request <a
href="https://redirect.github.com/indexmap-rs/indexmap/issues/417">#417</a>
from cuviper/release-2.11.3</li>
<li><a
href="9321145e1f"><code>9321145</code></a>
Release 2.11.3</li>
<li><a
href="7b485688c2"><code>7b48568</code></a>
Merge pull request <a
href="https://redirect.github.com/indexmap-rs/indexmap/issues/416">#416</a>
from cuviper/release-2.11.2</li>
<li><a
href="49ce7fa471"><code>49ce7fa</code></a>
Release 2.11.2</li>
<li><a
href="58fd834804"><code>58fd834</code></a>
Merge pull request <a
href="https://redirect.github.com/indexmap-rs/indexmap/issues/414">#414</a>
from DaniPopes/serde_core</li>
<li><a
href="5dc1d6ab31"><code>5dc1d6a</code></a>
Depend on <code>serde_core</code> instead of <code>serde</code></li>
<li>Additional commits viewable in <a
href="https://github.com/indexmap-rs/indexmap/compare/2.10.0...2.11.4">compare
view</a></li>
</ul>
</details>
<br />


[![Dependabot compatibility
score](https://dependabot-badges.githubapp.com/badges/compatibility_score?dependency-name=indexmap&package-manager=cargo&previous-version=2.10.0&new-version=2.11.4)](https://docs.github.com/en/github/managing-security-vulnerabilities/about-dependabot-security-updates#about-compatibility-scores)

Dependabot will resolve any conflicts with this PR as long as you don't
alter it yourself. You can also trigger a rebase manually by commenting
`@dependabot rebase`.

[//]: # (dependabot-automerge-start)
[//]: # (dependabot-automerge-end)

---

<details>
<summary>Dependabot commands and options</summary>
<br />

You can trigger Dependabot actions by commenting on this PR:
- `@dependabot rebase` will rebase this PR
- `@dependabot recreate` will recreate this PR, overwriting any edits
that have been made to it
- `@dependabot merge` will merge this PR after your CI passes on it
- `@dependabot squash and merge` will squash and merge this PR after
your CI passes on it
- `@dependabot cancel merge` will cancel a previously requested merge
and block automerging
- `@dependabot reopen` will reopen this PR if it is closed
- `@dependabot close` will close this PR and stop Dependabot recreating
it. You can achieve the same result by closing it manually
- `@dependabot show <dependency name> ignore conditions` will show all
of the ignore conditions of the specified dependency
- `@dependabot ignore this major version` will close this PR and stop
Dependabot creating any more for this major version (unless you reopen
the PR or upgrade to it yourself)
- `@dependabot ignore this minor version` will close this PR and stop
Dependabot creating any more for this minor version (unless you reopen
the PR or upgrade to it yourself)
- `@dependabot ignore this dependency` will close this PR and stop
Dependabot creating any more for this dependency (unless you reopen the
PR or upgrade to it yourself)


</details>

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: Eric Traut <etraut@openai.com>
2025-10-31 10:15:52 -07:00
dependabot[bot]
a2fe2f9fb1 chore(deps): bump anyhow from 1.0.99 to 1.0.100 in /codex-rs (#4802)
Bumps [anyhow](https://github.com/dtolnay/anyhow) from 1.0.99 to
1.0.100.
<details>
<summary>Release notes</summary>
<p><em>Sourced from <a
href="https://github.com/dtolnay/anyhow/releases">anyhow's
releases</a>.</em></p>
<blockquote>
<h2>1.0.100</h2>
<ul>
<li>Teach clippy to lint formatting arguments in <code>bail!</code>,
<code>ensure!</code>, <code>anyhow!</code> (<a
href="https://redirect.github.com/dtolnay/anyhow/issues/426">#426</a>)</li>
</ul>
</blockquote>
</details>
<details>
<summary>Commits</summary>
<ul>
<li><a
href="18c2598afa"><code>18c2598</code></a>
Release 1.0.100</li>
<li><a
href="f2719888cb"><code>f271988</code></a>
Merge pull request <a
href="https://redirect.github.com/dtolnay/anyhow/issues/426">#426</a>
from dtolnay/clippyfmt</li>
<li><a
href="52f2115a1f"><code>52f2115</code></a>
Mark macros with clippy::format_args</li>
<li><a
href="da5fd9d5a3"><code>da5fd9d</code></a>
Raise minimum tested compiler to rust 1.76</li>
<li><a
href="211e4092b7"><code>211e409</code></a>
Opt in to generate-macro-expansion when building on docs.rs</li>
<li><a
href="b48fc02c32"><code>b48fc02</code></a>
Enforce trybuild &gt;= 1.0.108</li>
<li><a
href="d5f59fbd45"><code>d5f59fb</code></a>
Update ui test suite to nightly-2025-09-07</li>
<li><a
href="238415d25b"><code>238415d</code></a>
Update ui test suite to nightly-2025-08-24</li>
<li><a
href="3bab0709a3"><code>3bab070</code></a>
Update actions/checkout@v4 -&gt; v5</li>
<li><a
href="42492546e3"><code>4249254</code></a>
Order cap-lints flag in the same order as thiserror build script</li>
<li>See full diff in <a
href="https://github.com/dtolnay/anyhow/compare/1.0.99...1.0.100">compare
view</a></li>
</ul>
</details>
<br />


[![Dependabot compatibility
score](https://dependabot-badges.githubapp.com/badges/compatibility_score?dependency-name=anyhow&package-manager=cargo&previous-version=1.0.99&new-version=1.0.100)](https://docs.github.com/en/github/managing-security-vulnerabilities/about-dependabot-security-updates#about-compatibility-scores)

Dependabot will resolve any conflicts with this PR as long as you don't
alter it yourself. You can also trigger a rebase manually by commenting
`@dependabot rebase`.

[//]: # (dependabot-automerge-start)
[//]: # (dependabot-automerge-end)

---

<details>
<summary>Dependabot commands and options</summary>
<br />

You can trigger Dependabot actions by commenting on this PR:
- `@dependabot rebase` will rebase this PR
- `@dependabot recreate` will recreate this PR, overwriting any edits
that have been made to it
- `@dependabot merge` will merge this PR after your CI passes on it
- `@dependabot squash and merge` will squash and merge this PR after
your CI passes on it
- `@dependabot cancel merge` will cancel a previously requested merge
and block automerging
- `@dependabot reopen` will reopen this PR if it is closed
- `@dependabot close` will close this PR and stop Dependabot recreating
it. You can achieve the same result by closing it manually
- `@dependabot show <dependency name> ignore conditions` will show all
of the ignore conditions of the specified dependency
- `@dependabot ignore this major version` will close this PR and stop
Dependabot creating any more for this major version (unless you reopen
the PR or upgrade to it yourself)
- `@dependabot ignore this minor version` will close this PR and stop
Dependabot creating any more for this minor version (unless you reopen
the PR or upgrade to it yourself)
- `@dependabot ignore this dependency` will close this PR and stop
Dependabot creating any more for this dependency (unless you reopen the
PR or upgrade to it yourself)


</details>

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: Eric Traut <etraut@openai.com>
2025-10-31 10:15:33 -07:00
dependabot[bot]
01ca2b5df6 chore(deps): bump actions/checkout from 4 to 5 (#4800)
Bumps [actions/checkout](https://github.com/actions/checkout) from 4 to
5.
<details>
<summary>Release notes</summary>
<p><em>Sourced from <a
href="https://github.com/actions/checkout/releases">actions/checkout's
releases</a>.</em></p>
<blockquote>
<h2>v5.0.0</h2>
<h2>What's Changed</h2>
<ul>
<li>Update actions checkout to use node 24 by <a
href="https://github.com/salmanmkc"><code>@​salmanmkc</code></a> in <a
href="https://redirect.github.com/actions/checkout/pull/2226">actions/checkout#2226</a></li>
<li>Prepare v5.0.0 release by <a
href="https://github.com/salmanmkc"><code>@​salmanmkc</code></a> in <a
href="https://redirect.github.com/actions/checkout/pull/2238">actions/checkout#2238</a></li>
</ul>
<h2>⚠️ Minimum Compatible Runner Version</h2>
<p><strong>v2.327.1</strong><br />
<a
href="https://github.com/actions/runner/releases/tag/v2.327.1">Release
Notes</a></p>
<p>Make sure your runner is updated to this version or newer to use this
release.</p>
<p><strong>Full Changelog</strong>: <a
href="https://github.com/actions/checkout/compare/v4...v5.0.0">https://github.com/actions/checkout/compare/v4...v5.0.0</a></p>
<h2>v4.3.0</h2>
<h2>What's Changed</h2>
<ul>
<li>docs: update README.md by <a
href="https://github.com/motss"><code>@​motss</code></a> in <a
href="https://redirect.github.com/actions/checkout/pull/1971">actions/checkout#1971</a></li>
<li>Add internal repos for checking out multiple repositories by <a
href="https://github.com/mouismail"><code>@​mouismail</code></a> in <a
href="https://redirect.github.com/actions/checkout/pull/1977">actions/checkout#1977</a></li>
<li>Documentation update - add recommended permissions to Readme by <a
href="https://github.com/benwells"><code>@​benwells</code></a> in <a
href="https://redirect.github.com/actions/checkout/pull/2043">actions/checkout#2043</a></li>
<li>Adjust positioning of user email note and permissions heading by <a
href="https://github.com/joshmgross"><code>@​joshmgross</code></a> in <a
href="https://redirect.github.com/actions/checkout/pull/2044">actions/checkout#2044</a></li>
<li>Update README.md by <a
href="https://github.com/nebuk89"><code>@​nebuk89</code></a> in <a
href="https://redirect.github.com/actions/checkout/pull/2194">actions/checkout#2194</a></li>
<li>Update CODEOWNERS for actions by <a
href="https://github.com/TingluoHuang"><code>@​TingluoHuang</code></a>
in <a
href="https://redirect.github.com/actions/checkout/pull/2224">actions/checkout#2224</a></li>
<li>Update package dependencies by <a
href="https://github.com/salmanmkc"><code>@​salmanmkc</code></a> in <a
href="https://redirect.github.com/actions/checkout/pull/2236">actions/checkout#2236</a></li>
<li>Prepare release v4.3.0 by <a
href="https://github.com/salmanmkc"><code>@​salmanmkc</code></a> in <a
href="https://redirect.github.com/actions/checkout/pull/2237">actions/checkout#2237</a></li>
</ul>
<h2>New Contributors</h2>
<ul>
<li><a href="https://github.com/motss"><code>@​motss</code></a> made
their first contribution in <a
href="https://redirect.github.com/actions/checkout/pull/1971">actions/checkout#1971</a></li>
<li><a href="https://github.com/mouismail"><code>@​mouismail</code></a>
made their first contribution in <a
href="https://redirect.github.com/actions/checkout/pull/1977">actions/checkout#1977</a></li>
<li><a href="https://github.com/benwells"><code>@​benwells</code></a>
made their first contribution in <a
href="https://redirect.github.com/actions/checkout/pull/2043">actions/checkout#2043</a></li>
<li><a href="https://github.com/nebuk89"><code>@​nebuk89</code></a> made
their first contribution in <a
href="https://redirect.github.com/actions/checkout/pull/2194">actions/checkout#2194</a></li>
<li><a href="https://github.com/salmanmkc"><code>@​salmanmkc</code></a>
made their first contribution in <a
href="https://redirect.github.com/actions/checkout/pull/2236">actions/checkout#2236</a></li>
</ul>
<p><strong>Full Changelog</strong>: <a
href="https://github.com/actions/checkout/compare/v4...v4.3.0">https://github.com/actions/checkout/compare/v4...v4.3.0</a></p>
<h2>v4.2.2</h2>
<h2>What's Changed</h2>
<ul>
<li><code>url-helper.ts</code> now leverages well-known environment
variables by <a href="https://github.com/jww3"><code>@​jww3</code></a>
in <a
href="https://redirect.github.com/actions/checkout/pull/1941">actions/checkout#1941</a></li>
<li>Expand unit test coverage for <code>isGhes</code> by <a
href="https://github.com/jww3"><code>@​jww3</code></a> in <a
href="https://redirect.github.com/actions/checkout/pull/1946">actions/checkout#1946</a></li>
</ul>
<p><strong>Full Changelog</strong>: <a
href="https://github.com/actions/checkout/compare/v4.2.1...v4.2.2">https://github.com/actions/checkout/compare/v4.2.1...v4.2.2</a></p>
<h2>v4.2.1</h2>
<h2>What's Changed</h2>
<ul>
<li>Check out other refs/* by commit if provided, fall back to ref by <a
href="https://github.com/orhantoy"><code>@​orhantoy</code></a> in <a
href="https://redirect.github.com/actions/checkout/pull/1924">actions/checkout#1924</a></li>
</ul>
<h2>New Contributors</h2>
<ul>
<li><a href="https://github.com/Jcambass"><code>@​Jcambass</code></a>
made their first contribution in <a
href="https://redirect.github.com/actions/checkout/pull/1919">actions/checkout#1919</a></li>
</ul>
<p><strong>Full Changelog</strong>: <a
href="https://github.com/actions/checkout/compare/v4.2.0...v4.2.1">https://github.com/actions/checkout/compare/v4.2.0...v4.2.1</a></p>
<!-- raw HTML omitted -->
</blockquote>
<p>... (truncated)</p>
</details>
<details>
<summary>Changelog</summary>
<p><em>Sourced from <a
href="https://github.com/actions/checkout/blob/main/CHANGELOG.md">actions/checkout's
changelog</a>.</em></p>
<blockquote>
<h1>Changelog</h1>
<h2>V5.0.0</h2>
<ul>
<li>Update actions checkout to use node 24 by <a
href="https://github.com/salmanmkc"><code>@​salmanmkc</code></a> in <a
href="https://redirect.github.com/actions/checkout/pull/2226">actions/checkout#2226</a></li>
</ul>
<h2>V4.3.0</h2>
<ul>
<li>docs: update README.md by <a
href="https://github.com/motss"><code>@​motss</code></a> in <a
href="https://redirect.github.com/actions/checkout/pull/1971">actions/checkout#1971</a></li>
<li>Add internal repos for checking out multiple repositories by <a
href="https://github.com/mouismail"><code>@​mouismail</code></a> in <a
href="https://redirect.github.com/actions/checkout/pull/1977">actions/checkout#1977</a></li>
<li>Documentation update - add recommended permissions to Readme by <a
href="https://github.com/benwells"><code>@​benwells</code></a> in <a
href="https://redirect.github.com/actions/checkout/pull/2043">actions/checkout#2043</a></li>
<li>Adjust positioning of user email note and permissions heading by <a
href="https://github.com/joshmgross"><code>@​joshmgross</code></a> in <a
href="https://redirect.github.com/actions/checkout/pull/2044">actions/checkout#2044</a></li>
<li>Update README.md by <a
href="https://github.com/nebuk89"><code>@​nebuk89</code></a> in <a
href="https://redirect.github.com/actions/checkout/pull/2194">actions/checkout#2194</a></li>
<li>Update CODEOWNERS for actions by <a
href="https://github.com/TingluoHuang"><code>@​TingluoHuang</code></a>
in <a
href="https://redirect.github.com/actions/checkout/pull/2224">actions/checkout#2224</a></li>
<li>Update package dependencies by <a
href="https://github.com/salmanmkc"><code>@​salmanmkc</code></a> in <a
href="https://redirect.github.com/actions/checkout/pull/2236">actions/checkout#2236</a></li>
</ul>
<h2>v4.2.2</h2>
<ul>
<li><code>url-helper.ts</code> now leverages well-known environment
variables by <a href="https://github.com/jww3"><code>@​jww3</code></a>
in <a
href="https://redirect.github.com/actions/checkout/pull/1941">actions/checkout#1941</a></li>
<li>Expand unit test coverage for <code>isGhes</code> by <a
href="https://github.com/jww3"><code>@​jww3</code></a> in <a
href="https://redirect.github.com/actions/checkout/pull/1946">actions/checkout#1946</a></li>
</ul>
<h2>v4.2.1</h2>
<ul>
<li>Check out other refs/* by commit if provided, fall back to ref by <a
href="https://github.com/orhantoy"><code>@​orhantoy</code></a> in <a
href="https://redirect.github.com/actions/checkout/pull/1924">actions/checkout#1924</a></li>
</ul>
<h2>v4.2.0</h2>
<ul>
<li>Add Ref and Commit outputs by <a
href="https://github.com/lucacome"><code>@​lucacome</code></a> in <a
href="https://redirect.github.com/actions/checkout/pull/1180">actions/checkout#1180</a></li>
<li>Dependency updates by <a
href="https://github.com/dependabot"><code>@​dependabot</code></a>- <a
href="https://redirect.github.com/actions/checkout/pull/1777">actions/checkout#1777</a>,
<a
href="https://redirect.github.com/actions/checkout/pull/1872">actions/checkout#1872</a></li>
</ul>
<h2>v4.1.7</h2>
<ul>
<li>Bump the minor-npm-dependencies group across 1 directory with 4
updates by <a
href="https://github.com/dependabot"><code>@​dependabot</code></a> in <a
href="https://redirect.github.com/actions/checkout/pull/1739">actions/checkout#1739</a></li>
<li>Bump actions/checkout from 3 to 4 by <a
href="https://github.com/dependabot"><code>@​dependabot</code></a> in <a
href="https://redirect.github.com/actions/checkout/pull/1697">actions/checkout#1697</a></li>
<li>Check out other refs/* by commit by <a
href="https://github.com/orhantoy"><code>@​orhantoy</code></a> in <a
href="https://redirect.github.com/actions/checkout/pull/1774">actions/checkout#1774</a></li>
<li>Pin actions/checkout's own workflows to a known, good, stable
version. by <a href="https://github.com/jww3"><code>@​jww3</code></a> in
<a
href="https://redirect.github.com/actions/checkout/pull/1776">actions/checkout#1776</a></li>
</ul>
<h2>v4.1.6</h2>
<ul>
<li>Check platform to set archive extension appropriately by <a
href="https://github.com/cory-miller"><code>@​cory-miller</code></a> in
<a
href="https://redirect.github.com/actions/checkout/pull/1732">actions/checkout#1732</a></li>
</ul>
<h2>v4.1.5</h2>
<ul>
<li>Update NPM dependencies by <a
href="https://github.com/cory-miller"><code>@​cory-miller</code></a> in
<a
href="https://redirect.github.com/actions/checkout/pull/1703">actions/checkout#1703</a></li>
<li>Bump github/codeql-action from 2 to 3 by <a
href="https://github.com/dependabot"><code>@​dependabot</code></a> in <a
href="https://redirect.github.com/actions/checkout/pull/1694">actions/checkout#1694</a></li>
<li>Bump actions/setup-node from 1 to 4 by <a
href="https://github.com/dependabot"><code>@​dependabot</code></a> in <a
href="https://redirect.github.com/actions/checkout/pull/1696">actions/checkout#1696</a></li>
<li>Bump actions/upload-artifact from 2 to 4 by <a
href="https://github.com/dependabot"><code>@​dependabot</code></a> in <a
href="https://redirect.github.com/actions/checkout/pull/1695">actions/checkout#1695</a></li>
<li>README: Suggest <code>user.email</code> to be
<code>41898282+github-actions[bot]@users.noreply.github.com</code> by <a
href="https://github.com/cory-miller"><code>@​cory-miller</code></a> in
<a
href="https://redirect.github.com/actions/checkout/pull/1707">actions/checkout#1707</a></li>
</ul>
<h2>v4.1.4</h2>
<ul>
<li>Disable <code>extensions.worktreeConfig</code> when disabling
<code>sparse-checkout</code> by <a
href="https://github.com/jww3"><code>@​jww3</code></a> in <a
href="https://redirect.github.com/actions/checkout/pull/1692">actions/checkout#1692</a></li>
<li>Add dependabot config by <a
href="https://github.com/cory-miller"><code>@​cory-miller</code></a> in
<a
href="https://redirect.github.com/actions/checkout/pull/1688">actions/checkout#1688</a></li>
<li>Bump the minor-actions-dependencies group with 2 updates by <a
href="https://github.com/dependabot"><code>@​dependabot</code></a> in <a
href="https://redirect.github.com/actions/checkout/pull/1693">actions/checkout#1693</a></li>
<li>Bump word-wrap from 1.2.3 to 1.2.5 by <a
href="https://github.com/dependabot"><code>@​dependabot</code></a> in <a
href="https://redirect.github.com/actions/checkout/pull/1643">actions/checkout#1643</a></li>
</ul>
<h2>v4.1.3</h2>
<!-- raw HTML omitted -->
</blockquote>
<p>... (truncated)</p>
</details>
<details>
<summary>Commits</summary>
<ul>
<li><a
href="08c6903cd8"><code>08c6903</code></a>
Prepare v5.0.0 release (<a
href="https://redirect.github.com/actions/checkout/issues/2238">#2238</a>)</li>
<li><a
href="9f265659d3"><code>9f26565</code></a>
Update actions checkout to use node 24 (<a
href="https://redirect.github.com/actions/checkout/issues/2226">#2226</a>)</li>
<li>See full diff in <a
href="https://github.com/actions/checkout/compare/v4...v5">compare
view</a></li>
</ul>
</details>
<br />


[![Dependabot compatibility
score](https://dependabot-badges.githubapp.com/badges/compatibility_score?dependency-name=actions/checkout&package-manager=github_actions&previous-version=4&new-version=5)](https://docs.github.com/en/github/managing-security-vulnerabilities/about-dependabot-security-updates#about-compatibility-scores)

Dependabot will resolve any conflicts with this PR as long as you don't
alter it yourself. You can also trigger a rebase manually by commenting
`@dependabot rebase`.

[//]: # (dependabot-automerge-start)
[//]: # (dependabot-automerge-end)

---

<details>
<summary>Dependabot commands and options</summary>
<br />

You can trigger Dependabot actions by commenting on this PR:
- `@dependabot rebase` will rebase this PR
- `@dependabot recreate` will recreate this PR, overwriting any edits
that have been made to it
- `@dependabot merge` will merge this PR after your CI passes on it
- `@dependabot squash and merge` will squash and merge this PR after
your CI passes on it
- `@dependabot cancel merge` will cancel a previously requested merge
and block automerging
- `@dependabot reopen` will reopen this PR if it is closed
- `@dependabot close` will close this PR and stop Dependabot recreating
it. You can achieve the same result by closing it manually
- `@dependabot show <dependency name> ignore conditions` will show all
of the ignore conditions of the specified dependency
- `@dependabot ignore this major version` will close this PR and stop
Dependabot creating any more for this major version (unless you reopen
the PR or upgrade to it yourself)
- `@dependabot ignore this minor version` will close this PR and stop
Dependabot creating any more for this minor version (unless you reopen
the PR or upgrade to it yourself)
- `@dependabot ignore this dependency` will close this PR and stop
Dependabot creating any more for this dependency (unless you reopen the
PR or upgrade to it yourself)


</details>

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-10-31 09:57:07 -07:00
dependabot[bot]
368f7adfc6 chore(deps): bump actions/github-script from 7 to 8 (#4801)
Bumps [actions/github-script](https://github.com/actions/github-script)
from 7 to 8.
<details>
<summary>Release notes</summary>
<p><em>Sourced from <a
href="https://github.com/actions/github-script/releases">actions/github-script's
releases</a>.</em></p>
<blockquote>
<h2>v8.0.0</h2>
<h2>What's Changed</h2>
<ul>
<li>Update Node.js version support to 24.x by <a
href="https://github.com/salmanmkc"><code>@​salmanmkc</code></a> in <a
href="https://redirect.github.com/actions/github-script/pull/637">actions/github-script#637</a></li>
<li>README for updating actions/github-script from v7 to v8 by <a
href="https://github.com/sneha-krip"><code>@​sneha-krip</code></a> in <a
href="https://redirect.github.com/actions/github-script/pull/653">actions/github-script#653</a></li>
</ul>
<h2>⚠️ Minimum Compatible Runner Version</h2>
<p><strong>v2.327.1</strong><br />
<a
href="https://github.com/actions/runner/releases/tag/v2.327.1">Release
Notes</a></p>
<p>Make sure your runner is updated to this version or newer to use this
release.</p>
<h2>New Contributors</h2>
<ul>
<li><a href="https://github.com/salmanmkc"><code>@​salmanmkc</code></a>
made their first contribution in <a
href="https://redirect.github.com/actions/github-script/pull/637">actions/github-script#637</a></li>
<li><a
href="https://github.com/sneha-krip"><code>@​sneha-krip</code></a> made
their first contribution in <a
href="https://redirect.github.com/actions/github-script/pull/653">actions/github-script#653</a></li>
</ul>
<p><strong>Full Changelog</strong>: <a
href="https://github.com/actions/github-script/compare/v7.1.0...v8.0.0">https://github.com/actions/github-script/compare/v7.1.0...v8.0.0</a></p>
<h2>v7.1.0</h2>
<h2>What's Changed</h2>
<ul>
<li>Upgrade husky to v9 by <a
href="https://github.com/benelan"><code>@​benelan</code></a> in <a
href="https://redirect.github.com/actions/github-script/pull/482">actions/github-script#482</a></li>
<li>Add workflow file for publishing releases to immutable action
package by <a
href="https://github.com/Jcambass"><code>@​Jcambass</code></a> in <a
href="https://redirect.github.com/actions/github-script/pull/485">actions/github-script#485</a></li>
<li>Upgrade IA Publish by <a
href="https://github.com/Jcambass"><code>@​Jcambass</code></a> in <a
href="https://redirect.github.com/actions/github-script/pull/486">actions/github-script#486</a></li>
<li>Fix workflow status badges by <a
href="https://github.com/joshmgross"><code>@​joshmgross</code></a> in <a
href="https://redirect.github.com/actions/github-script/pull/497">actions/github-script#497</a></li>
<li>Update usage of <code>actions/upload-artifact</code> by <a
href="https://github.com/joshmgross"><code>@​joshmgross</code></a> in <a
href="https://redirect.github.com/actions/github-script/pull/512">actions/github-script#512</a></li>
<li>Clear up package name confusion by <a
href="https://github.com/joshmgross"><code>@​joshmgross</code></a> in <a
href="https://redirect.github.com/actions/github-script/pull/514">actions/github-script#514</a></li>
<li>Update dependencies with <code>npm audit fix</code> by <a
href="https://github.com/joshmgross"><code>@​joshmgross</code></a> in <a
href="https://redirect.github.com/actions/github-script/pull/515">actions/github-script#515</a></li>
<li>Specify that the used script is JavaScript by <a
href="https://github.com/timotk"><code>@​timotk</code></a> in <a
href="https://redirect.github.com/actions/github-script/pull/478">actions/github-script#478</a></li>
<li>chore: Add Dependabot for NPM and Actions by <a
href="https://github.com/nschonni"><code>@​nschonni</code></a> in <a
href="https://redirect.github.com/actions/github-script/pull/472">actions/github-script#472</a></li>
<li>Define <code>permissions</code> in workflows and update actions by
<a href="https://github.com/joshmgross"><code>@​joshmgross</code></a> in
<a
href="https://redirect.github.com/actions/github-script/pull/531">actions/github-script#531</a></li>
<li>chore: Add Dependabot for .github/actions/install-dependencies by <a
href="https://github.com/nschonni"><code>@​nschonni</code></a> in <a
href="https://redirect.github.com/actions/github-script/pull/532">actions/github-script#532</a></li>
<li>chore: Remove .vscode settings by <a
href="https://github.com/nschonni"><code>@​nschonni</code></a> in <a
href="https://redirect.github.com/actions/github-script/pull/533">actions/github-script#533</a></li>
<li>ci: Use github/setup-licensed by <a
href="https://github.com/nschonni"><code>@​nschonni</code></a> in <a
href="https://redirect.github.com/actions/github-script/pull/473">actions/github-script#473</a></li>
<li>make octokit instance available as octokit on top of github, to make
it easier to seamlessly copy examples from GitHub rest api or octokit
documentations by <a
href="https://github.com/iamstarkov"><code>@​iamstarkov</code></a> in <a
href="https://redirect.github.com/actions/github-script/pull/508">actions/github-script#508</a></li>
<li>Remove <code>octokit</code> README updates for v7 by <a
href="https://github.com/joshmgross"><code>@​joshmgross</code></a> in <a
href="https://redirect.github.com/actions/github-script/pull/557">actions/github-script#557</a></li>
<li>docs: add &quot;exec&quot; usage examples by <a
href="https://github.com/neilime"><code>@​neilime</code></a> in <a
href="https://redirect.github.com/actions/github-script/pull/546">actions/github-script#546</a></li>
<li>Bump ruby/setup-ruby from 1.213.0 to 1.222.0 by <a
href="https://github.com/dependabot"><code>@​dependabot</code></a>[bot]
in <a
href="https://redirect.github.com/actions/github-script/pull/563">actions/github-script#563</a></li>
<li>Bump ruby/setup-ruby from 1.222.0 to 1.229.0 by <a
href="https://github.com/dependabot"><code>@​dependabot</code></a>[bot]
in <a
href="https://redirect.github.com/actions/github-script/pull/575">actions/github-script#575</a></li>
<li>Clearly document passing inputs to the <code>script</code> by <a
href="https://github.com/joshmgross"><code>@​joshmgross</code></a> in <a
href="https://redirect.github.com/actions/github-script/pull/603">actions/github-script#603</a></li>
<li>Update README.md by <a
href="https://github.com/nebuk89"><code>@​nebuk89</code></a> in <a
href="https://redirect.github.com/actions/github-script/pull/610">actions/github-script#610</a></li>
</ul>
<h2>New Contributors</h2>
<ul>
<li><a href="https://github.com/benelan"><code>@​benelan</code></a> made
their first contribution in <a
href="https://redirect.github.com/actions/github-script/pull/482">actions/github-script#482</a></li>
<li><a href="https://github.com/Jcambass"><code>@​Jcambass</code></a>
made their first contribution in <a
href="https://redirect.github.com/actions/github-script/pull/485">actions/github-script#485</a></li>
<li><a href="https://github.com/timotk"><code>@​timotk</code></a> made
their first contribution in <a
href="https://redirect.github.com/actions/github-script/pull/478">actions/github-script#478</a></li>
<li><a
href="https://github.com/iamstarkov"><code>@​iamstarkov</code></a> made
their first contribution in <a
href="https://redirect.github.com/actions/github-script/pull/508">actions/github-script#508</a></li>
<li><a href="https://github.com/neilime"><code>@​neilime</code></a> made
their first contribution in <a
href="https://redirect.github.com/actions/github-script/pull/546">actions/github-script#546</a></li>
<li><a href="https://github.com/nebuk89"><code>@​nebuk89</code></a> made
their first contribution in <a
href="https://redirect.github.com/actions/github-script/pull/610">actions/github-script#610</a></li>
</ul>
<p><strong>Full Changelog</strong>: <a
href="https://github.com/actions/github-script/compare/v7...v7.1.0">https://github.com/actions/github-script/compare/v7...v7.1.0</a></p>
<!-- raw HTML omitted -->
</blockquote>
<p>... (truncated)</p>
</details>
<details>
<summary>Commits</summary>
<ul>
<li><a
href="ed597411d8"><code>ed59741</code></a>
Merge pull request <a
href="https://redirect.github.com/actions/github-script/issues/653">#653</a>
from actions/sneha-krip/readme-for-v8</li>
<li><a
href="2dc352e4ba"><code>2dc352e</code></a>
Bold minimum Actions Runner version in README</li>
<li><a
href="01e118c8d0"><code>01e118c</code></a>
Update README for Node 24 runtime requirements</li>
<li><a
href="8b222ac82e"><code>8b222ac</code></a>
Apply suggestion from <a
href="https://github.com/salmanmkc"><code>@​salmanmkc</code></a></li>
<li><a
href="adc0eeac99"><code>adc0eea</code></a>
README for updating actions/github-script from v7 to v8</li>
<li><a
href="20fe497b3f"><code>20fe497</code></a>
Merge pull request <a
href="https://redirect.github.com/actions/github-script/issues/637">#637</a>
from actions/node24</li>
<li><a
href="e7b7f222b1"><code>e7b7f22</code></a>
update licenses</li>
<li><a
href="2c81ba05f3"><code>2c81ba0</code></a>
Update Node.js version support to 24.x</li>
<li>See full diff in <a
href="https://github.com/actions/github-script/compare/v7...v8">compare
view</a></li>
</ul>
</details>
<br />


[![Dependabot compatibility
score](https://dependabot-badges.githubapp.com/badges/compatibility_score?dependency-name=actions/github-script&package-manager=github_actions&previous-version=7&new-version=8)](https://docs.github.com/en/github/managing-security-vulnerabilities/about-dependabot-security-updates#about-compatibility-scores)

Dependabot will resolve any conflicts with this PR as long as you don't
alter it yourself. You can also trigger a rebase manually by commenting
`@dependabot rebase`.

[//]: # (dependabot-automerge-start)
[//]: # (dependabot-automerge-end)

---

<details>
<summary>Dependabot commands and options</summary>
<br />

You can trigger Dependabot actions by commenting on this PR:
- `@dependabot rebase` will rebase this PR
- `@dependabot recreate` will recreate this PR, overwriting any edits
that have been made to it
- `@dependabot merge` will merge this PR after your CI passes on it
- `@dependabot squash and merge` will squash and merge this PR after
your CI passes on it
- `@dependabot cancel merge` will cancel a previously requested merge
and block automerging
- `@dependabot reopen` will reopen this PR if it is closed
- `@dependabot close` will close this PR and stop Dependabot recreating
it. You can achieve the same result by closing it manually
- `@dependabot show <dependency name> ignore conditions` will show all
of the ignore conditions of the specified dependency
- `@dependabot ignore this major version` will close this PR and stop
Dependabot creating any more for this major version (unless you reopen
the PR or upgrade to it yourself)
- `@dependabot ignore this minor version` will close this PR and stop
Dependabot creating any more for this minor version (unless you reopen
the PR or upgrade to it yourself)
- `@dependabot ignore this dependency` will close this PR and stop
Dependabot creating any more for this dependency (unless you reopen the
PR or upgrade to it yourself)


</details>

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-10-31 09:56:33 -07:00
Shijie Rao
68731ac74d fix: brew upgrade link (#6045)
### Summary
Fix brew upgrade FAQ link
2025-10-31 09:51:33 -07:00
jif-oai
0508823075 test: undo (#6034) 2025-10-31 14:46:24 +00:00
dependabot[bot]
2ac14d1145 chore(deps): bump thiserror from 2.0.16 to 2.0.17 in /codex-rs (#4426)
Bumps [thiserror](https://github.com/dtolnay/thiserror) from 2.0.16 to
2.0.17.
<details>
<summary>Release notes</summary>
<p><em>Sourced from <a
href="https://github.com/dtolnay/thiserror/releases">thiserror's
releases</a>.</em></p>
<blockquote>
<h2>2.0.17</h2>
<ul>
<li>Use differently named __private module per patch release (<a
href="https://redirect.github.com/dtolnay/thiserror/issues/434">#434</a>)</li>
</ul>
</blockquote>
</details>
<details>
<summary>Commits</summary>
<ul>
<li><a
href="72ae716e6d"><code>72ae716</code></a>
Release 2.0.17</li>
<li><a
href="599fdce83a"><code>599fdce</code></a>
Merge pull request <a
href="https://redirect.github.com/dtolnay/thiserror/issues/434">#434</a>
from dtolnay/private</li>
<li><a
href="9ec05f6b38"><code>9ec05f6</code></a>
Use differently named __private module per patch release</li>
<li><a
href="d2c492b549"><code>d2c492b</code></a>
Raise minimum tested compiler to rust 1.76</li>
<li><a
href="fc3ab9501d"><code>fc3ab95</code></a>
Opt in to generate-macro-expansion when building on docs.rs</li>
<li><a
href="819fe29dbb"><code>819fe29</code></a>
Update ui test suite to nightly-2025-09-12</li>
<li><a
href="259f48c549"><code>259f48c</code></a>
Enforce trybuild &gt;= 1.0.108</li>
<li><a
href="470e6a681c"><code>470e6a6</code></a>
Update ui test suite to nightly-2025-08-24</li>
<li><a
href="544e191e6e"><code>544e191</code></a>
Update actions/checkout@v4 -&gt; v5</li>
<li><a
href="cbc1ebad3e"><code>cbc1eba</code></a>
Delete duplicate cap-lints flag from build script</li>
<li>See full diff in <a
href="https://github.com/dtolnay/thiserror/compare/2.0.16...2.0.17">compare
view</a></li>
</ul>
</details>
<br />


[![Dependabot compatibility
score](https://dependabot-badges.githubapp.com/badges/compatibility_score?dependency-name=thiserror&package-manager=cargo&previous-version=2.0.16&new-version=2.0.17)](https://docs.github.com/en/github/managing-security-vulnerabilities/about-dependabot-security-updates#about-compatibility-scores)

You can trigger a rebase of this PR by commenting `@dependabot rebase`.

[//]: # (dependabot-automerge-start)
[//]: # (dependabot-automerge-end)

---

<details>
<summary>Dependabot commands and options</summary>
<br />

You can trigger Dependabot actions by commenting on this PR:
- `@dependabot rebase` will rebase this PR
- `@dependabot recreate` will recreate this PR, overwriting any edits
that have been made to it
- `@dependabot merge` will merge this PR after your CI passes on it
- `@dependabot squash and merge` will squash and merge this PR after
your CI passes on it
- `@dependabot cancel merge` will cancel a previously requested merge
and block automerging
- `@dependabot reopen` will reopen this PR if it is closed
- `@dependabot close` will close this PR and stop Dependabot recreating
it. You can achieve the same result by closing it manually
- `@dependabot show <dependency name> ignore conditions` will show all
of the ignore conditions of the specified dependency
- `@dependabot ignore this major version` will close this PR and stop
Dependabot creating any more for this major version (unless you reopen
the PR or upgrade to it yourself)
- `@dependabot ignore this minor version` will close this PR and stop
Dependabot creating any more for this minor version (unless you reopen
the PR or upgrade to it yourself)
- `@dependabot ignore this dependency` will close this PR and stop
Dependabot creating any more for this dependency (unless you reopen the
PR or upgrade to it yourself)


</details>

> **Note**
> Automatic rebases have been disabled on this pull request as it has
been open for over 30 days.

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: Eric Traut <etraut@openai.com>
2025-10-30 19:00:00 -07:00
pakrym-oai
2371d771cc Update user instruction message format (#6010) 2025-10-30 18:44:02 -07:00
Huaiwu Li
9a638dbf4e fix(tui): propagate errors in insert_history_lines_to_writer (#4266)
## What?
Fixed error handling in `insert_history_lines_to_writer` where all
terminal operations were silently ignoring errors via `.ok()`.

  ## Why?
Silent I/O failures could leave the terminal in an inconsistent state
(e.g., scroll region not reset) with no way to debug. This violates Rust
error handling best practices.

  ## How?
  - Changed function signature to return `io::Result<()>`
  - Replaced all `.ok()` calls with `?` operator to propagate errors
- Added `tracing::warn!` in wrapper function for backward compatibility
  - Updated 15 test call sites to handle Result  with `.expect()`

  ## Testing
  -  Pass all tests

  ## Type of Change
  - [x] Bug fix (non-breaking change)

---------

Signed-off-by: Huaiwu Li <lhwzds@gmail.com>
Co-authored-by: Eric Traut <etraut@openai.com>
2025-10-30 18:07:51 -07:00
Ahmed Ibrahim
dc2aeac21f override verbosity for gpt-5-codex (#6007)
we are seeing [reports](https://github.com/openai/codex/issues/6004) of
users having verbosity in their config.toml and facing issues.
gpt-5-codex doesn't accept other values rather than medium for
verbosity.
2025-10-31 00:45:05 +00:00
Jack
f842849bec docs: Fix markdown list item spacing in codex-rs/core/review_prompt.md (#4144)
Fixes a Markdown parsing issue where a list item used `*` without a
following space (`*Line ranges ...`). Per CommonMark, a space after the
list marker is required. Updated to `* Line ranges ...` so the guideline
renders as a standalone bullet. This change improves readability and
prevents mis-parsing in renderers.

Co-authored-by: Eric Traut <etraut@openai.com>
2025-10-30 17:39:21 -07:00
zhao-oai
dcf73970d2 rate limit errors now provide absolute time (#6000) 2025-10-30 20:33:25 -04:00
Ahmed Ibrahim
e761924dc2 feat: add exit slash command alias for quit (#6002)
## Summary
- add the `/exit` slash command alongside `/quit` and reuse shared exit
handling
- refactor the chat widget to funnel quit, exit, logout, and shutdown
flows through a common `request_exit` helper
- add focused unit tests that confirm both `/quit` and `/exit` send an
`ExitRequest`

## Testing
- `just fmt`
- `just fix -p codex-tui`
- `cargo test -p codex-tui`


------
https://chatgpt.com/codex/tasks/task_i_6903d5a8f47c8321bf180f031f2fa330
2025-10-30 17:29:40 -07:00
Owen Lin
cdc3df3790 [app-server] refactor: split API types into v1 and v2 (#6005)
Makes it easier to figure out which types are defined in the old vs. new
API schema.
2025-10-30 23:56:55 +00:00
Ahmed Ibrahim
a3d3719481 Remove last turn reasoning filtering (#5986) 2025-10-30 23:20:32 +00:00
Jeremy Rose
11e5327770 build: 8mb stacks on win (#5997)
#5981 seems to be fixing what's actually a call stack overflow, maybe
this will fix it without disabling a feature?
2025-10-30 16:12:50 -07:00
iceweasel-oai
87cce88f48 Windows Sandbox - Alpha version (#4905)
- Added the new codex-windows-sandbox crate that builds both a library
entry point (run_windows_sandbox_capture) and a CLI executable to launch
commands inside a Windows restricted-token sandbox, including ACL
management, capability SID provisioning, network lockdown, and output
capture
(windows-sandbox-rs/src/lib.rs:167, windows-sandbox-rs/src/main.rs:54).
- Introduced the experimental WindowsSandbox feature flag and wiring so
Windows builds can opt into the sandbox:
SandboxType::WindowsRestrictedToken, the in-process execution path, and
platform sandbox selection now honor the flag (core/src/features.rs:47,
core/src/config.rs:1224, core/src/safety.rs:19,
core/src/sandboxing/mod.rs:69, core/src/exec.rs:79,
core/src/exec.rs:172).
- Updated workspace metadata to include the new crate and its
Windows-specific dependencies so the core crate can link against it
(codex-rs/
    Cargo.toml:91, core/Cargo.toml:86).
- Added a PowerShell bootstrap script that installs the Windows
toolchain, required CLI utilities, and builds the workspace to ease
development
    on the platform (scripts/setup-windows.ps1:1).
- Landed a Python smoke-test suite that exercises
read-only/workspace-write policies, ACL behavior, and network denial for
the Windows sandbox
    binary (windows-sandbox-rs/sandbox_smoketests.py:1).
2025-10-30 15:51:57 -07:00
Bernard Niset
ff6d4cec6b fix: Update seatbelt policy for java on macOS (#3987)
# Summary

This PR is related to the Issue #3978 and contains a fix to the seatbelt
profile for macOS that allows to run java/jdk tooling from the sandbox.
I have found that the included change is the minimum change to make it
run on my machine.

There is a unit test added by codex when making this fix. I wonder if it
is useful since you need java installed on the target machine for it to
be relevant. I can remove it it is better.

Fixes #3978
2025-10-30 14:25:04 -07:00
Celia Chen
6ef658a9f9 [Hygiene] Remove include_view_image_tool config (#5976)
There's still some debate about whether we want to expose
`tools.view_image` or `feature.view_image` so those are left unchanged
for now, but this old `include_view_image_tool` config is good-to-go.
Also updated the doc to reflect that `view_image` tool is now by default
true.
2025-10-30 13:23:24 -07:00
Brad M. Harris
8b8be343a7 Documentation improvement: add missing period (#3754)
Pull request template, minimal:

---

### **What?**

Minor change (low-hanging fruit).

### **Why?**

To improve code quality or documentation with minimal risk and effort.

### **How?**

Edited directly via VSCode Editor.

---

**Checklist (pre-PR):**

* [x] I have read the CLA Document and hereby sign the CLA.
* [x] I reviewed the “Contributing” markdown file for this project.

*This template meets standard external (non-OpenAI) PR requirements and
signals compliance for maintainers.*

Co-authored-by: Eric Traut <etraut@openai.com>
2025-10-30 13:01:33 -07:00
Owen Lin
89c00611c2 [app-server] remove serde(skip_serializing_if = "Option::is_none") annotations (#5939)
We had this annotation everywhere in app-server APIs which made it so
that fields get serialized as `field?: T`, meaning if the field as
`None` we would omit the field in the payload. Removing this annotation
changes it so that we return `field: T | null` instead, which makes
codex app-server's API more aligned with the convention of public OpenAI
APIs like Responses.

Separately, remove the `#[ts(optional_fields = nullable)]` annotations
that were recently added which made all the TS types become `field?: T |
null` which is not great since clients need to handle undefined and
null.

I think generally it'll be best to have optional types be either:
- `field: T | null` (preferred, aligned with public OpenAI APIs)
- `field?: T` where we have to, such as types generated from the MCP
schema:
https://github.com/modelcontextprotocol/modelcontextprotocol/blob/main/schema/2025-06-18/schema.ts
(see changes to `mcp-types/`)

I updated @etraut-openai's unit test to check that all generated TS
types are one or the other, not both (so will error if we have a type
that has `field?: T | null`). I don't think there's currently a good use
case for that - but we can always revisit.
2025-10-30 18:18:53 +00:00
Anton Panasenko
9572cfc782 [codex] add developer instructions (#5897)
we are using developer instructions for code reviews, we need to pass
them in cli as well.
2025-10-30 11:18:31 -07:00
Dylan Hurd
4a55646a02 chore: testing on freeform apply_patch (#5952)
## Summary
Duplicates the tests in `apply_patch_cli.rs`, but tests the freeform
apply_patch tool as opposed to the function call path. The good news is
that all the tests pass with zero logical tests, with the exception of
the heredoc, which doesn't really make sense in the freeform tool
context anyway.

@jif-oai since you wrote the original tests in #5557, I'd love your
opinion on the right way to DRY these test cases between the two. Happy
to set up a more sophisticated harness, but didn't want to go down the
rabbit hole until we agreed on the right pattern

## Testing
- [x] These are tests
2025-10-30 10:40:48 -07:00
jif-oai
209af68611 nit: log rmcp_client (#5978) 2025-10-30 17:40:38 +00:00
jif-oai
f4f9695978 feat: compaction prompt configurable (#5959)
```
 codex -c compact_prompt="Summarize in bullet points"
 ```
2025-10-30 14:24:24 +00:00
Ahmed Ibrahim
5fcc380bd9 Pass initial history as an optional to codex delegate (#5950)
This will give us more freedom on controlling the delegation. i.e we can
fork our history and run `compact`.
2025-10-30 07:22:42 -07:00
jif-oai
aa76003e28 chore: unify config crates (#5958) 2025-10-30 10:28:32 +00:00
Ahmed Ibrahim
fac548e430 Send delegate header (#5942)
Send delegate type header
2025-10-30 09:49:40 +00:00
Ahmed Ibrahim
9bd3453592 Add debug-only slash command for rollout path (#5936)
## Summary
- add a debug-only `/rollout` slash command that prints the rollout file
path or reports when none is known
- surface the new command in the slash command metadata and cover it
with unit tests

<img width="539" height="99" alt="image"
src="https://github.com/user-attachments/assets/688e1334-8a06-4576-abb8-ada33b458661"
/>
2025-10-30 03:51:00 +00:00
zhao-oai
b34efde2f3 asdf (#5940)
.
2025-10-30 01:10:41 +00:00
Ahmed Ibrahim
7aa46ab5fc ignore agent message deltas for the review mode (#5937)
The deltas produce the whole json output. ignore them.
2025-10-30 00:47:55 +00:00
pakrym-oai
bf35105af6 Re-enable SDK image forwarding test (#5934)
## Summary
- re-enable the TypeScript SDK test that verifies local images are
forwarded to `codex exec`

## Testing
- `pnpm test` *(fails: unable to download pnpm 10.8.1 because external
network access is blocked in the sandbox)*

------
https://chatgpt.com/codex/tasks/task_i_690289cb861083209fd006867e2adfb1
2025-10-29 23:18:26 +00:00
pakrym-oai
3429e82e45 Add item streaming events (#5546)
Adds AgentMessageContentDelta, ReasoningContentDelta,
ReasoningRawContentDelta item streaming events while maintaining
compatibility for old events.

---------

Co-authored-by: Owen Lin <owen@openai.com>
2025-10-29 22:33:57 +00:00
pakrym-oai
815ae4164a [exec] Add MCP tool arguments and results (#5899)
Extends mcp_tool_call item to include arguments and results.
2025-10-29 14:23:57 -07:00
Ahmed Ibrahim
13e1d0362d Delegate review to codex instance (#5572)
In this PR, I am exploring migrating task kind to an invocation of
Codex. The main reason would be getting rid off multiple
`ConversationHistory` state and streamlining our context/history
management.

This approach depends on opening a channel between the sub-codex and
codex. This channel is responsible for forwarding `interactive`
(`approvals`) and `non-interactive` events. The `task` is responsible
for handling those events.

This opens the door for implementing `codex as a tool`, replacing
`compact` and `review`, and potentially subagents.

One consideration is this code is very similar to `app-server` specially
in the approval part. If in the future we wanted an interactive
`sub-codex` we should consider using `codex-mcp`
2025-10-29 21:04:25 +00:00
jif-oai
db31f6966d chore: config editor (#5878)
The goal is to have a single place where we actually write files

In a follow-up PR, will move everything config related in a dedicated
module and move the helpers in a dedicated file
2025-10-29 20:52:46 +00:00
jif-oai
2b20cd66af fix: icu_decimal version (#5919) 2025-10-29 20:46:45 +00:00
Rasmus Rygaard
39e09c289d Add a wrapper around raw response items (#5923)
We currently have nested enums when sending raw response items in the
app-server protocol. This makes downstream schemas confusing because we
need to embed `type`-discriminated enums within each other.

This PR adds a small wrapper around the response item so we can keep the
schemas separate
2025-10-29 20:32:40 +00:00
Eric Traut
069a38a06c Add missing "nullable" macro to protocol structs that contain optional fields (#5901)
This PR addresses a current hole in the TypeScript code generation for
the API server protocol. Fields that are marked as "Optional<>" in the
Rust code are serialized such that the value is omitted when it is
deserialized — appearing as `undefined`, but the TS type indicates
(incorrectly) that it is always defined but possibly `null`. This can
lead to subtle errors that the TypeScript compiler doesn't catch. The
fix is to include the `#[ts(optional_fields = nullable)]` macro for all
protocol structs that contain one or more `Optional<>` fields.

This PR also includes a new test that validates that all TS protocol
code containing "| null" in its type is marked optional ("?") to catch
cases where `#[ts(optional_fields = nullable)]` is omitted.
2025-10-29 12:09:47 -07:00
jif-oai
3183935bd7 feat: add output even in sandbox denied (#5908) 2025-10-29 18:21:18 +00:00
jif-oai
060637b4d4 feat: deprecation warning (#5825)
<img width="955" height="311" alt="Screenshot 2025-10-28 at 14 26 25"
src="https://github.com/user-attachments/assets/99729b3d-3bc9-4503-aab3-8dc919220ab4"
/>
2025-10-29 12:29:28 +00:00
jif-oai
fa92cd92fa chore: merge git crates (#5909)
Merge `git-apply` and `git-tooling` into `utils/`
2025-10-29 12:11:44 +00:00
Abhishek Bhardwaj
89591e4246 feature: Add "!cmd" user shell execution (#2471)
feature: Add "!cmd" user shell execution

This change lets users run local shell commands directly from the TUI by
prefixing their input with ! (e.g. !ls). Output is truncated to keep the
exec cell usable, and Ctrl-C cleanly
  interrupts long-running commands (e.g. !sleep 10000).

**Summary of changes**

- Route Op::RunUserShellCommand through a dedicated UserShellCommandTask
(core/src/tasks/user_shell.rs), keeping the task logic out of codex.rs.
- Reuse the existing tool router: the task constructs a ToolCall for the
local_shell tool and relies on ShellHandler, so no manual MCP tool
lookup is required.
- Emit exec lifecycle events (ExecCommandBegin/ExecCommandEnd) so the
TUI can show command metadata, live output, and exit status.

**End-to-end flow**

  **TUI handling**

1. ChatWidget::submit_user_message (TUI) intercepts messages starting
with !.
2. Non-empty commands dispatch Op::RunUserShellCommand { command };
empty commands surface a help hint.
3. No UserInput items are created, so nothing is enqueued for the model.

  **Core submission loop**
4. The submission loop routes the op to handlers::run_user_shell_command
(core/src/codex.rs).
5. A fresh TurnContext is created and Session::spawn_user_shell_command
enqueues UserShellCommandTask.

  **Task execution**
6. UserShellCommandTask::run emits TaskStartedEvent, formats the
command, and prepares a ToolCall targeting local_shell.
  7. ToolCallRuntime::handle_tool_call dispatches to ShellHandler.

  **Shell tool runtime**
8. ShellHandler::run_exec_like launches the process via the unified exec
runtime, honoring sandbox and shell policies, and emits
ExecCommandBegin/End.
9. Stdout/stderr are captured for the UI, but the task does not turn the
resulting ToolOutput into a model response.

  **Completion**
10. After ExecCommandEnd, the task finishes without an assistant
message; the session marks it complete and the exec cell displays the
final output.

  **Conversation context**

- The command and its output never enter the conversation history or the
model prompt; the flow is local-only.
  - Only exec/task events are emitted for UI rendering.

**Demo video**


https://github.com/user-attachments/assets/fcd114b0-4304-4448-a367-a04c43e0b996
2025-10-29 00:31:20 -07:00
Axojhf
802d2440b4 Fix bash detection failure in VS Code Codex extension on Windows under certain conditions (#3421)
Found that the VS Code Codex extension throws “Error starting
conversation” when initializing a conversation with Git for Windows’
bash on PATH.
Debugging showed the bash-detection logic did not return as expected;
this change makes it reliable in that scenario.
Possibly related to issue #2841.
2025-10-28 21:29:16 -07:00
Curt
e9135fa7c5 fix(windows-path): preserve PATH order; include core env vars (#5579)
# Preserve PATH precedence & fix Windows MCP env propagation

## Problem & intent

Preserve user PATH precedence and reduce Windows setup friction for MCP
servers by avoiding PATH reordering and ensuring Windows child processes
receive essential env vars.

- Addresses: #4180 #5225 #2945 #3245 #3385 #2892 #3310 #3457 #4370  
- Supersedes: #4182, #3866, #3828 (overlapping/inferior once this
merges)
- Notes: #2626 / #2646 are the original PATH-mutation sources being
corrected.

---

## Before / After

**Before**  
- PATH was **prepended** with an `apply_patch` helper dir (Rust + Node
wrapper), reordering tools and breaking virtualenvs/shims on
macOS/Linux.
- On Windows, MCP servers missed core env vars and often failed to start
without explicit per-server env blocks.

**After**  
- Helper dir is **appended** to PATH (preserves user/tool precedence).  
- Windows MCP child env now includes common core variables and mirrors
`PATH` → `Path`, so typical CLIs/plugins work **without** per-server env
blocks.

---

## Scope of change

### `codex-rs/arg0/src/lib.rs`
- Append temp/helper dir to `PATH` instead of prepending.

### `codex-cli/bin/codex.js`
- Mirror the same append behavior for the Node wrapper.

### `codex-rs/rmcp-client/src/utils.rs`
- Expand Windows `DEFAULT_ENV_VARS` (e.g., `COMSPEC`, `SYSTEMROOT`,
`PROGRAMFILES*`, `APPDATA`, etc.).
- Mirror `PATH` → `Path` for Windows child processes.  
- Small unit test; conditional `mut` + `clippy` cleanup.

---

## Security effects

No broadened privileges. Only environment propagation for well-known
Windows keys on stdio MCP child processes. No sandbox policy changes and
no network additions.

---

## Testing evidence

**Static**  
- `cargo fmt`  
- `cargo clippy -p codex-arg0 -D warnings` → **clean**  
- `cargo clippy -p codex-rmcp-client -D warnings` → **clean**  
- `cargo test -p codex-rmcp-client` → **13 passed**

**Manual**  
- Local verification on Windows PowerShell 5/7 and WSL (no `unused_mut`
warnings on non-Windows targets).

---

## Checklist

- [x] Append (not prepend) helper dir to PATH in Rust and Node wrappers
- [x] Windows MCP child inherits core env vars; `PATH` mirrored to
`Path`
- [x] `cargo fmt` / `clippy` clean across touched crates  
- [x] Unit tests updated/passing where applicable  
- [x] Cross-platform behavior preserved (macOS/Linux PATH precedence
intact)
2025-10-28 21:06:39 -07:00
pakrym-oai
ef3e075ad6 Refresh tokens more often and log a better message when both auth and token refresh fails (#5655)
<img width="784" height="153" alt="image"
src="https://github.com/user-attachments/assets/c44b0eb2-d65c-4fc2-8b54-b34f7e1c4d95"
/>
2025-10-28 18:55:53 -07:00
Anton Panasenko
149e198ce8 [codex][app-server] resume conversation from history (#5893) 2025-10-28 18:18:03 -07:00
Gabriel Peal
1d76ba5ebe [App Server] Allow fetching or resuming a conversation summary from the conversation id (#5890)
This PR adds an option to app server to allow conversation summaries to
be fetched from just the conversation id rather than rollout path for
convenience at the cost of some latency to discover the rollout path.

This convenience is non-trivial as it allows app servers to simply
maintain conversation ids rather than rollout paths and the associated
platform (Windows) handling associated with storing and encoding them
correctly.
2025-10-28 20:17:22 -04:00
Rasmus Rygaard
a1635eea25 [app-server] Annotate more exported types with a title (#5879)
Follow-up to https://github.com/openai/codex/pull/5063

Refined the app-server export pipeline so JSON Schema variants and
discriminator fields are annotated with descriptive, stable titles
before writing the bundle. This eliminates anonymous enum names in the
generated Pydantic models (goodbye Type7) while keeping downstream
tooling simple. Added shared helpers to derive titles and literals, and
reused them across the traversal logic for clarity. Running just fix -p
codex-app-server-protocol, just fmt, and cargo test -p
codex-app-server-protocol validates the change.
2025-10-28 16:35:12 -07:00
zhao-oai
36113509f2 verify mime type of images (#5888)
solves: https://github.com/openai/codex/issues/5675

Block non-image uploads in the view_image workflow. We now confirm the
file’s MIME is image/* before building the data URL; otherwise we emit a
“unsupported MIME type” error to the model. This stops the agent from
sending application/json blobs that the Responses API rejects with 400s.

<img width="409" height="556" alt="Screenshot 2025-10-28 at 1 15 10 PM"
src="https://github.com/user-attachments/assets/a92199e8-2769-4b1d-8e33-92d9238c90fe"
/>
2025-10-28 14:52:51 -07:00
Eric Traut
ba95d9862c Fixed bug that results in a sporadic hang when attaching images (#5891)
Addresses https://github.com/openai/codex/issues/5773

Testing: I tested that images work (regardless of order that they are
associated with the task prompt) in both the CLI and Extension. Also
verified that conversations in CLI and extension with images can be
resumed.
2025-10-28 14:42:46 -07:00
Ahmed Ibrahim
ef55992ab0 remove beta experimental header (#5892) 2025-10-28 21:28:56 +00:00
Ahmed Ibrahim
e3f913f567 revert #5812 release file (#5887)
revert #5812 release file
2025-10-28 20:06:16 +00:00
pakrym-oai
1b8f2543ac Filter out reasoning items from previous turns (#5857)
Reduces request size and prevents 400 errors when switching between API
orgs.

Based on Responses API behavior described in
https://cookbook.openai.com/examples/responses_api/reasoning_items#caching
2025-10-28 11:39:34 -07:00
Jeremy Rose
65107d24a2 Fix handling of non-main default branches for cloud task submissions (#5069)
## Summary
- detect the repository's default branch before submitting a cloud task
- expose a helper in `codex_core::git_info` for retrieving the default
branch name

Fixes #4888


------
https://chatgpt.com/codex/tasks/task_i_68e96093cf28832ca0c9c73fc618a309
2025-10-28 11:02:25 -07:00
Jeremy Rose
36eb071998 tui: show queued messages during response stream (#5540)
This fixes an issue where messages sent during the final response stream
would seem to disappear, because the "queued messages" UI wasn't shown
during streaming.
2025-10-28 16:59:19 +00:00
Jeremy Rose
9b33ce3409 tui: wait longer for color query results (#5004)
this bumps the timeout when reading the responses to OSC 10/11 so that
we're less likely to pass the deadline halfway through reading the
response.
2025-10-28 09:42:57 -07:00
zhao-oai
926c89cb20 fix advanced.md (#5833)
table wasn't formatting correctly
2025-10-28 16:32:20 +00:00
jif-oai
5ba2a17576 chore: decompose submission loop (#5854) 2025-10-28 15:23:46 +00:00
Owen Lin
266419217e chore: use anyhow::Result for all app-server integration tests (#5836)
There's a lot of visual noise in app-server's integration tests due to
the number of `.expect("<some_msg>")` lines which are largely redundant
/ not very useful. Clean them up by using `anyhow::Result` + `?`
consistently.

Replaces the existing pattern of:
```
    let codex_home = TempDir::new().expect("create temp dir");
    create_config_toml(codex_home.path()).expect("write config.toml");

    let mut mcp = McpProcess::new(codex_home.path())
        .await
        .expect("spawn mcp process");
    timeout(DEFAULT_READ_TIMEOUT, mcp.initialize())
        .await
        .expect("initialize timeout")
        .expect("initialize request");
```

With:
```
    let codex_home = TempDir::new()?;
    create_config_toml(codex_home.path())?;

    let mut mcp = McpProcess::new(codex_home.path()).await?;
    timeout(DEFAULT_READ_TIMEOUT, mcp.initialize()).await??;
```
2025-10-28 08:10:23 -07:00
jif-oai
be4bdfec93 chore: drop useless shell stuff (#5848) 2025-10-28 14:52:52 +00:00
jif-oai
7ff142d93f chore: speed-up pipeline (#5812)
Speed-up pipeline by:
* Decoupling tests and clippy
* Use pre-built binary in tests
* `sccache` for caching of the builds
2025-10-28 14:08:52 +00:00
Celia Chen
4a42c4e142 [Auth] Choose which auth storage to use based on config (#5792)
This PR is a follow-up to #5591. It allows users to choose which auth
storage mode they want by using the new
`cli_auth_credentials_store_mode` config.
2025-10-27 19:41:49 -07:00
Josh McKinney
66a4b89822 feat(tui): clarify Windows auto mode requirements (#5568)
## Summary
- Coerce Windows `workspace-write` configs back to read-only, surface
the forced downgrade in the approvals popup,
  and funnel users toward WSL or Full Access.
- Add WSL installation instructions to the Auto preset on Windows while
keeping the preset available for other
  platforms.
- Skip the trust-on-first-run prompt on native Windows so new folders
remain read-only without additional
  confirmation.
- Expose a structured sandbox policy resolution from config to flag
Windows downgrades and adjust tests (core,
exec, TUI) to reflect the new behavior; provide a Windows-only approvals
snapshot.

  ## Testing
  - cargo fmt
- cargo test -p codex-core
config::tests::add_dir_override_extends_workspace_writable_roots
- cargo test -p codex-exec
suite::resume::exec_resume_preserves_cli_configuration_overrides
- cargo test -p codex-tui
chatwidget::tests::approvals_selection_popup_snapshot
- cargo test -p codex-tui
approvals_popup_includes_wsl_note_for_auto_mode
  - cargo test -p codex-tui windows_skips_trust_prompt
  - just fix -p codex-core
  - just fix -p codex-tui
2025-10-28 01:19:32 +00:00
Ahmed Ibrahim
d7b333be97 Truncate the content-item for mcp tools (#5835)
This PR truncates the text output of MCP tool
2025-10-28 00:39:35 +00:00
zhao-oai
4d6a42a622 fix image drag drop (#5794)
fixing drag/drop photos bug in codex

state of the world before:

sometimes, when you drag screenshots into codex, the image does not
properly render into context. instead, the file name is shown in
quotation marks.


https://github.com/user-attachments/assets/3c0e540a-505c-4ec0-b634-e9add6a73119

the screenshot is not actually included in agent context. the agent
needs to manually call the view_image tool to see the screenshot. this
can be unreliable especially if the image is part of a longer prompt and
is dependent on the agent going out of its way to view the image.

state of the world after:


https://github.com/user-attachments/assets/5f2b7bf7-8a3f-4708-85f3-d68a017bfd97

now, images will always be directly embedded into chat context

## Technical Details

- MacOS sends screenshot paths with a narrow no‑break space right before
the “AM/PM” suffix, which used to trigger our non‑ASCII fallback in the
paste burst detector.
- That fallback flushed the partially buffered paste immediately, so the
path arrived in two separate `handle_paste` calls (quoted prefix +
`PM.png'`). The split string could not be normalized to a real path, so
we showed the quoted filename instead of embedding the image.
- We now append non‑ASCII characters into the burst buffer when a burst
is already active. Finder’s payload stays intact, the path normalizes,
and the image attaches automatically.
- When no burst is active (e.g. during IME typing), non‑ASCII characters
still bypass the buffer so text entry remains responsive.
2025-10-27 17:11:30 -07:00
Gabriel Peal
b0bdc04c30 [MCP] Render MCP tool call result images to the model (#5600)
It's pretty amazing we have gotten here without the ability for the
model to see image content from MCP tool calls.

This PR builds off of 4391 and fixes #4819. I would like @KKcorps to get
adequete credit here but I also want to get this fix in ASAP so I gave
him a week to update it and haven't gotten a response so I'm going to
take it across the finish line.


This test highlights how absured the current situation is. I asked the
model to read this image using the Chrome MCP
<img width="2378" height="674" alt="image"
src="https://github.com/user-attachments/assets/9ef52608-72a2-4423-9f5e-7ae36b2b56e0"
/>

After this change, it correctly outputs:
> Captured the page: image dhows a dark terminal-style UI labeled
`OpenAI Codex (v0.0.0)` with prompt `model: gpt-5-codex medium` and
working directory `/codex/codex-rs`
(and more)  

Before this change, it said:
> Took the full-page screenshot you asked for. It shows a long,
horizontally repeating pattern of stylized people in orange, light-blue,
and mustard clothing, holding hands in alternating poses against a white
background. No text or other graphics-just rows of flat illustration
stretching off to the right.

Without this change, the Figma, Playwright, Chrome, and other visual MCP
servers are pretty much entirely useless.

I tested this change with the openai respones api as well as a third
party completions api
2025-10-27 17:55:57 -04:00
Owen Lin
67a219ffc2 fix: move account struct to app-server-protocol and use camelCase (#5829)
Makes sense to move this struct to `app-server-protocol/` since we want
to serialize as camelCase, but we don't for structs defined in
`protocol/`

It was:
```
export type Account = { "type": "ApiKey", api_key: string, } | { "type": "chatgpt", email: string | null, plan_type: PlanType, };
```

But we want:
```
export type Account = { "type": "apiKey", apiKey: string, } | { "type": "chatgpt", email: string | null, planType: PlanType, };
```
2025-10-27 14:06:13 -07:00
Ahmed Ibrahim
7226365397 Centralize truncation in conversation history (#5652)
move the truncation logic to conversation history to use on any tool
output. This will help us in avoiding edge cases while truncating the
tool calls and mcp calls.
2025-10-27 14:05:35 -07:00
Celia Chen
0fc295d958 [Auth] Add keyring support for Codex CLI (#5591)
Follow-up PR to #5569. Add Keyring Support for Auth Storage in Codex CLI
as well as a hybrid mode (default to persisting in keychain but fall
back to file when unavailable.)

It also refactors out the keyringstore implementation from rmcp-client
[here](https://github.com/openai/codex/blob/main/codex-rs/rmcp-client/src/oauth.rs)
to a new keyring-store crate.

There will be a follow-up that picks the right credential mode depending
on the config, instead of hardcoding `AuthCredentialsStoreMode::File`.
2025-10-27 12:10:11 -07:00
jif-oai
3e50f94d76 feat: support verbosity in model_family (#5821) 2025-10-27 18:46:30 +00:00
Celia Chen
eb5b1b627f [Auth] Introduce New Auth Storage Abstraction for Codex CLI (#5569)
This PR introduces a new `Auth Storage` abstraction layer that takes
care of read, write, and load of auth tokens based on the
AuthCredentialsStoreMode. It is similar to how we handle MCP client
oauth
[here](https://github.com/openai/codex/blob/main/codex-rs/rmcp-client/src/oauth.rs).
Instead of reading and writing directly from disk for auth tokens, Codex
CLI workflows now should instead use this auth storage using the public
helper functions.

This PR is just a refactor of the current code so the behavior stays the
same. We will add support for keyring and hybrid mode in follow-up PRs.

I have read the CLA Document and I hereby sign the CLA
2025-10-27 11:01:14 -07:00
Eric Traut
0c1ff1d3fd Made token refresh code resilient to missing id_token (#5782)
This PR does the following:
1. Changes `try_refresh_token` to handle the case where the endpoint
returns a response without an `id_token`. The OpenID spec indicates that
this field is optional and clients should not assume it's present.
2. Changes the `attempt_stream_responses` to propagate token refresh
errors rather than silently ignoring them.
3. Fixes a typo in a couple of error messages (unrelated to the above,
but something I noticed in passing) - "reconnect" should be spelled
without a hyphen.

This PR does not implement the additional suggestion from @pakrym-oai
that we should sign out when receiving `refresh_token_expired` from the
refresh endpoint. Leaving this as a follow-on because I'm undecided on
whether this should be implemented in `try_refresh_token` or its
callers.
2025-10-27 10:09:53 -07:00
jif-oai
aea7610c76 feat: image resizing (#5446)
Add image resizing on the client side to reduce load on the API
2025-10-27 16:58:10 +00:00
jif-oai
775fbba6e0 feat: return an error if unknown enabled/disabled feature (#5817) 2025-10-27 16:53:00 +00:00
Michael Bolin
5ee8a17b4e feat: introduce GetConversationSummary RPC (#5803)
This adds an RPC to the app server to the the `ConversationSummary` via
a rollout path. Now that the VS Code extension supports showing the
Codex UI in an editor panel where the URI of the panel maps to the
rollout file, we need to be able to get the `ConversationSummary` from
the rollout file directly.
2025-10-27 09:11:45 -07:00
jif-oai
81be54b229 fix: test yield time (#5811) 2025-10-27 11:57:29 +00:00
jif-oai
5e8659dcbc chore: undo nits (#5631) 2025-10-27 11:48:01 +00:00
jif-oai
2338294b39 nit: doc on session task (#5809) 2025-10-27 11:43:33 +00:00
jif-oai
afc4eaab8b feat: TUI undo op (#5629) 2025-10-27 10:55:29 +00:00
jif-oai
e92c4f6561 feat: async ghost commit (#5618) 2025-10-27 10:09:10 +00:00
Michael Bolin
15fa2283e7 feat: update NewConversationParams to take an optional model_provider (#5793)
An AppServer client should be able to use any (`model_provider`, `model`) in the user's config. `NewConversationParams` already supported specifying the `model`, but this PR expands it to support `model_provider`, as well.

---
[//]: # (BEGIN SAPLING FOOTER)
Stack created with [Sapling](https://sapling-scm.com). Best reviewed
with [ReviewStack](https://reviewstack.dev/openai/codex/pull/5793).
* #5803
* __->__ #5793
2025-10-27 09:33:30 +00:00
Michael Bolin
5907422d65 feat: annotate conversations with model_provider for filtering (#5658)
Because conversations that use the Responses API can have encrypted
reasoning messages, trying to resume a conversation with a different
provider could lead to confusing "failed to decrypt" errors. (This is
reproducible by starting a conversation using ChatGPT login and resuming
it as a conversation that uses OpenAI models via Azure.)

This changes `ListConversationsParams` to take a `model_providers:
Option<Vec<String>>` and adds `model_provider` on each
`ConversationSummary` it returns so these cases can be disambiguated.

Note this ended up making changes to
`codex-rs/core/src/rollout/tests.rs` because it had a number of cases
where it expected `Some` for the value of `next_cursor`, but the list of
rollouts was complete, so according to this docstring:


bcd64c7e72/codex-rs/app-server-protocol/src/protocol.rs (L334-L337)

If there are no more items to return, then `next_cursor` should be
`None`. This PR updates that logic.






---
[//]: # (BEGIN SAPLING FOOTER)
Stack created with [Sapling](https://sapling-scm.com). Best reviewed
with [ReviewStack](https://reviewstack.dev/openai/codex/pull/5658).
* #5803
* #5793
* __->__ #5658
2025-10-27 02:03:30 -07:00
Ahmed Ibrahim
f178805252 Add feedback upload request handling (#5682) 2025-10-27 05:53:39 +00:00
Michael Bolin
a55b0c4bcc fix: revert "[app-server] fix account/read response annotation (#5642)" (#5796)
Revert #5642 because this generates:

```
// GENERATED CODE! DO NOT MODIFY BY HAND!

// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.

export type GetAccountResponse = Account | null;
```

But `Account` is unknown.

The unique use of `#[ts(export)]` on `GetAccountResponse` is also
suspicious as are the changes to
`codex-rs/app-server-protocol/src/export.rs` since the existing system
has worked fine for quite some time.

Though a pure backout of #5642 puts things in a state where, as the PR
noted, the following does not work:

```
cargo run -p codex-app-server-protocol --bin export -- --out DIR
```

So in addition to the backout, this PR adds:

```rust
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)]
#[serde(rename_all = "camelCase")]
pub struct GetAccountResponse {
    pub account: Account,
}
```

and changes `GetAccount.response` as follows:

```diff
-        response: Option<Account>,
+        response: GetAccountResponse,
```

making it consistent with other types.

With this change, I verified that both of the following work:

```
just codex generate-ts --out /tmp/somewhere
cargo run -p codex-app-server-protocol --bin export -- --out /tmp/somewhere-else
```

The generated TypeScript is as follows:

```typescript
// GetAccountResponse.ts
import type { Account } from "./Account";

export type GetAccountResponse = { account: Account, };
```

and

```typescript
// Account.ts
import type { PlanType } from "./PlanType";

export type Account = { "type": "ApiKey", api_key: string, } | { "type": "chatgpt", email: string | null, plan_type: PlanType, };
```

Though while the inconsistency between `"type": "ApiKey"` and `"type":
"chatgpt"` is quite concerning, I'm not sure if that format is ever
written to disk in any case, but @owenlin0, I would recommend looking
into that.

Also, it appears that the types in `codex-rs/protocol/src/account.rs`
are used exclusively by the `app-server-protocol` crate, so perhaps they
should just be moved there?
2025-10-26 18:57:42 -07:00
Thibault Sottiaux
224222f09f fix: use codex-exp prefix for experimental models and consider codex- models to be production (#5797) 2025-10-27 01:55:12 +00:00
Gabriel Peal
7aab45e060 [MCP] Minor docs clarifications around stdio tokens (#5676)
Noticed
[here](https://github.com/openai/codex/issues/4707#issuecomment-3446547561)
2025-10-26 13:38:30 -04:00
Eric Traut
bcd64c7e72 Reduced runtime of unit test that was taking multiple minutes (#5688)
Modified `build_compacted_history_truncates_overlong_user_messages` test
to reduce runtime from minutes to tens of seconds
2025-10-25 23:46:08 -07:00
Eric Traut
c124f24354 Added support for sandbox_mode in profiles (#5686)
Currently, `approval_policy` is supported in profiles, but
`sandbox_mode` is not. This PR adds support for `sandbox_mode`.

Note: a fix for this was submitted in [this
PR](https://github.com/openai/codex/pull/2397), but the underlying code
has changed significantly since then.

This addresses issue #3034
2025-10-25 16:52:26 -07:00
pakrym-oai
c7e4e6d0ee Skip flaky test (#5680)
Did an investigation but couldn't find anything obvious. Let's skip for
now.
2025-10-25 12:11:16 -07:00
Ahmed Ibrahim
88abbf58ce Followup feedback (#5663)
- Added files to be uploaded
- Refactored
- Updated title
2025-10-25 06:07:40 +00:00
Ahmed Ibrahim
71f838389b Improve feedback (#5661)
<img width="1099" height="153" alt="image"
src="https://github.com/user-attachments/assets/2c901884-8baf-4b1b-b2c4-bcb61ff42be8"
/>

<img width="1082" height="125" alt="image"
src="https://github.com/user-attachments/assets/6336e6c9-9ace-46df-a383-a807ceffa524"
/>

<img width="1102" height="103" alt="image"
src="https://github.com/user-attachments/assets/78883682-7e44-4fa3-9e04-57f7df4766fd"
/>
2025-10-24 22:28:14 -07:00
Eric Traut
0533bd2e7c Fixed flaky unit test (#5654)
This PR fixes a test that is sporadically failing in CI.

The problem is that two unit tests (the older `login_and_cancel_chatgpt`
and a recently added
`login_chatgpt_includes_forced_workspace_query_param`) exercise code
paths that start the login server. The server binds to a hard-coded
localhost port number, so attempts to start more than one server at the
same time will fail. If these two tests happen to run concurrently, one
of them will fail.

To fix this, I've added a simple mutex. We can use this same mutex for
future tests that use the same pattern.
2025-10-24 16:31:24 -07:00
Anton Panasenko
6af83d86ff [codex][app-server] introduce codex/event/raw_item events (#5578) 2025-10-24 22:41:52 +00:00
Gabriel Peal
e2e1b65da6 [MCP] Properly gate login after mcp add with experimental_use_rmcp_client (#5653)
There was supposed to be a check here like in other places.
2025-10-24 18:32:15 -04:00
Gabriel Peal
817d1508bc [MCP] Redact environment variable values in /mcp and mcp get (#5648)
Fixes #5524
2025-10-24 18:30:20 -04:00
Eric Traut
f8af4f5c8d Added model summary and risk assessment for commands that violate sandbox policy (#5536)
This PR adds support for a model-based summary and risk assessment for
commands that violate the sandbox policy and require user approval. This
aids the user in evaluating whether the command should be approved.

The feature works by taking a failed command and passing it back to the
model and asking it to summarize the command, give it a risk level (low,
medium, high) and a risk category (e.g. "data deletion" or "data
exfiltration"). It uses a new conversation thread so the context in the
existing thread doesn't influence the answer. If the call to the model
fails or takes longer than 5 seconds, it falls back to the current
behavior.

For now, this is an experimental feature and is gated by a config key
`experimental_sandbox_command_assessment`.

Here is a screen shot of the approval prompt showing the risk assessment
and summary.

<img width="723" height="282" alt="image"
src="https://github.com/user-attachments/assets/4597dd7c-d5a0-4e9f-9d13-414bd082fd6b"
/>
2025-10-24 15:23:44 -07:00
pakrym-oai
a4be4d78b9 Log more types of request IDs (#5645)
Different services return different sets of IDs, log all of them to
simplify debugging.
2025-10-24 19:12:03 +00:00
Shijie Rao
00c1de0c56 Add instruction for upgrading codex with brew (#5640)
Include instruction for upgrading codex with brew when there is switch
from formula to cask.
2025-10-24 11:30:34 -07:00
Owen Lin
190e7eb104 [app-server] fix account/read response annotation (#5642)
The API schema export is currently broken:
```
> cargo run -p codex-app-server-protocol --bin export -- --out DIR
Error: this type cannot be exported
```

This PR fixes the error message so we get more info:
```
> cargo run -p codex-app-server-protocol --bin export -- --out DIR
Error: failed to export client responses: dependency core::option::Option<codex_protocol::account::Account> cannot be exported
```

And fixes the root cause which is the `account/read` response.
2025-10-24 11:17:46 -07:00
pakrym-oai
061862a0e2 Add CodexHttpClient wrapper with request logging (#5564)
## Summary
- wrap the default reqwest::Client inside a new
CodexHttpClient/CodexRequestBuilder pair and log the HTTP method, URL,
and status for each request
- update the auth/model/provider plumbing to use the new builder helpers
so headers and bearer auth continue to be applied consistently
- add the shared `http` dependency that backs the header conversion
helpers

## Testing
- `CODEX_SANDBOX=seatbelt CODEX_SANDBOX_NETWORK_DISABLED=1 cargo test -p
codex-core`
- `CODEX_SANDBOX=seatbelt CODEX_SANDBOX_NETWORK_DISABLED=1 cargo test -p
codex-chatgpt`
- `CODEX_SANDBOX=seatbelt CODEX_SANDBOX_NETWORK_DISABLED=1 cargo test -p
codex-tui`

------
https://chatgpt.com/codex/tasks/task_i_68fa5038c17483208b1148661c5873be
2025-10-24 09:47:52 -07:00
zhao-oai
c72b2ad766 adding messaging for stale rate limits + when no rate limits are cached (#5570) 2025-10-24 08:46:31 -07:00
jif-oai
80783a7bb9 fix: flaky tests (#5625) 2025-10-24 13:56:41 +01:00
Gabriel Peal
ed77d2d977 [MCP] Improve startup errors for timeouts and github (#5595)
1. I have seen too many reports of people hitting startup timeout errors
and thinking Codex is broken. Hopefully this will help people
self-serve. We may also want to consider raising the timeout to ~15s.
2. Make it more clear what PAT is (personal access token) in the GitHub
error

<img width="2378" height="674" alt="CleanShot 2025-10-23 at 22 05 06"
src="https://github.com/user-attachments/assets/d148ce1d-ade3-4511-84a4-c164aefdb5c5"
/>
2025-10-24 01:54:45 -04:00
Gabriel Peal
abccd3e367 [MCP] Update rmcp to 0.8.3 (#5542)
Picks up modelcontextprotocol/rust-sdk#497 which fixes #5208 by allowing 204 response to MCP initialize notifications instead of just 202.
2025-10-23 20:45:29 -07:00
Ahmed Ibrahim
0f4fd33ddd Moving token_info to ConversationHistory (#5581)
I want to centralize input processing and management to
`ConversationHistory`. This would need `ConversationHistory` to have
access to `token_info` (i.e. preventing adding a big input to the
history). Besides, it makes more sense to have it on
`ConversationHistory` than `state`.
2025-10-23 20:30:58 -07:00
Josh McKinney
e258f0f044 Use Option symbol for mac key hints (#5582)
## Summary
- show the Option (⌥) symbol in key hints when the TUI is built for
macOS so the shortcut text matches the platform terminology

## Testing
- cargo test -p codex-tui

------
https://chatgpt.com/codex/tasks/task_i_68fab7505530832992780a9e13fb707b
2025-10-23 20:04:15 -07:00
jif-oai
a6b9471548 feat: end events on unified exec (#5551) 2025-10-23 18:51:34 +01:00
Thibault Sottiaux
3059373e06 fix: resume lookup for gitignored CODEX_HOME (#5311)
Walk the sessions tree instead of using file_search so gitignored
CODEX_HOME directories can resume sessions. Add a regression test that
covers a .gitignore'd sessions directory.

Fixes #5247
Fixes #5412

---------

Co-authored-by: Owen Lin <owen@openai.com>
2025-10-23 17:04:40 +00:00
jif-oai
0b4527146e feat: use actual tokenizer for unified_exec truncation (#5514) 2025-10-23 17:08:06 +01:00
jif-oai
6745b12427 chore: testing on apply_path (#5557) 2025-10-23 17:00:48 +01:00
Ahmed Ibrahim
f59978ed3d Handle cancelling/aborting while processing a turn (#5543)
Currently we collect all all turn items in a vector, then we add it to
the history on success. This result in losing those items on errors
including aborting `ctrl+c`.

This PR:
- Adds the ability for the tool call to handle cancellation
- bubble the turn items up to where we are recording this info

Admittedly, this logic is an ad-hoc logic that doesn't handle a lot of
error edge cases. The right thing to do is recording to the history on
the spot as `items`/`tool calls output` come. However, this isn't
possible because of having different `task_kind` that has different
`conversation_histories`. The `try_run_turn` has no idea what thread are
we using. We cannot also pass an `arc` to the `conversation_histories`
because it's a private element of `state`.

That's said, `abort` is the most common case and we should cover it
until we remove `task kind`
2025-10-23 08:47:10 -07:00
Jeremy Rose
3ab6028e80 tui: show aggregated output in display (#5539)
This shows the aggregated (stdout + stderr) buffer regardless of exit
code.

Many commands output useful / relevant info on stdout when returning a
non-zero exit code, or the same on stderr when returning an exit code of
0. Often, useful info is present on both stdout AND stderr. Also, the
model sees both. So it is confusing to see commands listed as "(no
output)" that in fact do have output, just on the stream that doesn't
match the exit status, or to see some sort of trivial output like "Tests
failed" but lacking any information about the actual failure.

As such, always display the aggregated output in the display. Transcript
mode remains unchanged as it was already displaying the text that the
model sees, which seems correct for transcript mode.
2025-10-23 08:05:08 -07:00
jif-oai
892eaff46d fix: approval issue (#5525) 2025-10-23 11:13:53 +01:00
jif-oai
8e291a1706 chore: clean handle_container_exec_with_params (#5516)
Drop `handle_container_exec_with_params` to have simpler and more
straight forward execution path
2025-10-23 09:24:01 +01:00
Owen Lin
aee321f62b [app-server] add new account method API stubs (#5527)
These are the schema definitions for the new JSON-RPC APIs associated
with accounts. These are not wired up to business logic yet and will
currently throw an internal error indicating these are unimplemented.
2025-10-22 15:36:11 -07:00
Genki Takiuchi
ed32da04d7 Fix IME submissions dropping leading digits (#4359)
- ensure paste burst flush preserves ASCII characters before IME commits
- add regression test covering digit followed by Japanese text
submission

Fixes openai/codex#4356

Co-authored-by: Josh McKinney <joshka@openai.com>
2025-10-22 22:18:17 +00:00
Owen Lin
8ae3949072 [app-server] send account/rateLimits/updated notifications (#5477)
Codex will now send an `account/rateLimits/updated` notification
whenever the user's rate limits are updated.

This is implemented by just transforming the existing TokenCount event.
2025-10-22 20:12:40 +00:00
Ahmed Ibrahim
273819aaae Move changing turn input functionalities to ConversationHistory (#5473)
We are doing some ad-hoc logic while dealing with conversation history.
Ideally, we shouldn't mutate `vec[responseitem]` manually at all and
should depend on `ConversationHistory` for those changes.

Those changes are:
- Adding input to the history
- Removing items from the history
- Correcting history

I am also adding some `error` logs for cases we shouldn't ideally face.
For example, we shouldn't be missing `toolcalls` or `outputs`. We
shouldn't hit `ContextWindowExceeded` while performing `compact`

This refactor will give us granular control over our context management.
2025-10-22 13:08:46 -07:00
Gabriel Peal
4cd6b01494 [MCP] Remove the legacy stdio client in favor of rmcp (#5529)
I haven't heard of any issues with the studio rmcp client so let's
remove the legacy one and default to the new one.

Any code changes are moving code from the adapter inline but there
should be no meaningful functionality changes.
2025-10-22 12:06:59 -07:00
Thibault Sottiaux
dd59b16a17 docs: fix agents fallback example (#5396) 2025-10-22 11:32:35 -07:00
jif-oai
bac7acaa7c chore: clean spec tests (#5517) 2025-10-22 18:30:33 +01:00
pakrym-oai
3c90728a29 Add new thread items and rewire event parsing to use them (#5418)
1. Adds AgentMessage,  Reasoning,  WebSearch items.
2. Switches the ResponseItem parsing to use new items and then also emit
3. Removes user-item kind and filters out "special" (environment) user
items when returning to clients.
2025-10-22 10:14:50 -07:00
Gabriel Peal
34c5a9eaa9 [MCP] Add support for specifying scopes for MCP oauth (#5487)
```
codex mcp login server_name --scopes=scope1,scope2,scope3
```

Fixes #5480
2025-10-22 09:37:33 -07:00
jif-oai
f522aafb7f chore: drop approve all (#5503)
Not needed anymore
2025-10-22 16:55:06 +01:00
jif-oai
fd0673e457 feat: local tokenizer (#5508) 2025-10-22 16:01:02 +01:00
jif-oai
00b1e130b3 chore: align unified_exec (#5442)
Align `unified_exec` with b implementation
2025-10-22 11:50:18 +01:00
1863 changed files with 214252 additions and 54625 deletions

3
.bazelignore Normal file
View File

@@ -0,0 +1,3 @@
# Without this, Bazel will consider BUILD.bazel files in
# .git/sl/origbackups (which can be populated by Sapling SCM).
.git

45
.bazelrc Normal file
View File

@@ -0,0 +1,45 @@
common --repo_env=BAZEL_DO_NOT_DETECT_CPP_TOOLCHAIN=1
common --repo_env=BAZEL_NO_APPLE_CPP_TOOLCHAIN=1
common --disk_cache=~/.cache/bazel-disk-cache
common --repo_contents_cache=~/.cache/bazel-repo-contents-cache
common --repository_cache=~/.cache/bazel-repo-cache
common --experimental_platform_in_output_dir
common --enable_platform_specific_config
# TODO(zbarsky): We need to untangle these libc constraints to get linux remote builds working.
common:linux --host_platform=//:local
common --@rules_cc//cc/toolchains/args/archiver_flags:use_libtool_on_macos=False
common --@toolchains_llvm_bootstrapped//config:experimental_stub_libgcc_s
# We need to use the sh toolchain on windows so we don't send host bash paths to the linux executor.
common:windows --@rules_rust//rust/settings:experimental_use_sh_toolchain_for_bootstrap_process_wrapper
# TODO(zbarsky): rules_rust doesn't implement this flag properly with remote exec...
# common --@rules_rust//rust/settings:pipelined_compilation
common --incompatible_strict_action_env
# Not ideal, but We need to allow dotslash to be found
common --test_env=PATH=/opt/homebrew/bin:/usr/local/bin:/usr/bin:/bin:/usr/sbin:/sbin
common --test_output=errors
common --bes_results_url=https://app.buildbuddy.io/invocation/
common --bes_backend=grpcs://remote.buildbuddy.io
common --remote_cache=grpcs://remote.buildbuddy.io
common --remote_download_toplevel
common --nobuild_runfile_links
common --remote_timeout=3600
common --noexperimental_throttle_remote_action_building
common --experimental_remote_execution_keepalive
common --grpc_keepalive_time=30s
# This limits both in-flight executions and concurrent downloads. Even with high number
# of jobs execution will still be limited by CPU cores, so this just pays a bit of
# memory in exchange for higher download concurrency.
common --jobs=30
common:remote --extra_execution_platforms=//:rbe
common:remote --remote_executor=grpcs://remote.buildbuddy.io
common:remote --jobs=800

View File

@@ -1 +1,3 @@
iTerm
iTerm2
psuedo

View File

@@ -3,4 +3,4 @@
skip = .git*,vendor,*-lock.yaml,*.lock,.codespellrc,*test.ts,*.jsonl,frame*.txt
check-hidden = true
ignore-regex = ^\s*"image/\S+": ".*|\b(afterAll)\b
ignore-words-list = ratatui,ser
ignore-words-list = ratatui,ser,iTerm,iterm2,iterm

View File

@@ -0,0 +1,44 @@
name: linux-code-sign
description: Sign Linux artifacts with cosign.
inputs:
target:
description: Target triple for the artifacts to sign.
required: true
artifacts-dir:
description: Absolute path to the directory containing built binaries to sign.
required: true
runs:
using: composite
steps:
- name: Install cosign
uses: sigstore/cosign-installer@v3.7.0
- name: Cosign Linux artifacts
shell: bash
env:
COSIGN_EXPERIMENTAL: "1"
COSIGN_YES: "true"
COSIGN_OIDC_CLIENT_ID: "sigstore"
COSIGN_OIDC_ISSUER: "https://oauth2.sigstore.dev/auth"
run: |
set -euo pipefail
dest="${{ inputs.artifacts-dir }}"
if [[ ! -d "$dest" ]]; then
echo "Destination $dest does not exist"
exit 1
fi
for binary in codex codex-responses-api-proxy; do
artifact="${dest}/${binary}"
if [[ ! -f "$artifact" ]]; then
echo "Binary $artifact not found"
exit 1
fi
cosign sign-blob \
--yes \
--bundle "${artifact}.sigstore" \
"$artifact"
done

View File

@@ -0,0 +1,246 @@
name: macos-code-sign
description: Configure, sign, notarize, and clean up macOS code signing artifacts.
inputs:
target:
description: Rust compilation target triple (e.g. aarch64-apple-darwin).
required: true
sign-binaries:
description: Whether to sign and notarize the macOS binaries.
required: false
default: "true"
sign-dmg:
description: Whether to sign and notarize the macOS dmg.
required: false
default: "true"
apple-certificate:
description: Base64-encoded Apple signing certificate (P12).
required: true
apple-certificate-password:
description: Password for the signing certificate.
required: true
apple-notarization-key-p8:
description: Base64-encoded Apple notarization key (P8).
required: true
apple-notarization-key-id:
description: Apple notarization key ID.
required: true
apple-notarization-issuer-id:
description: Apple notarization issuer ID.
required: true
runs:
using: composite
steps:
- name: Configure Apple code signing
shell: bash
env:
KEYCHAIN_PASSWORD: actions
APPLE_CERTIFICATE: ${{ inputs.apple-certificate }}
APPLE_CERTIFICATE_PASSWORD: ${{ inputs.apple-certificate-password }}
run: |
set -euo pipefail
if [[ -z "${APPLE_CERTIFICATE:-}" ]]; then
echo "APPLE_CERTIFICATE is required for macOS signing"
exit 1
fi
if [[ -z "${APPLE_CERTIFICATE_PASSWORD:-}" ]]; then
echo "APPLE_CERTIFICATE_PASSWORD is required for macOS signing"
exit 1
fi
cert_path="${RUNNER_TEMP}/apple_signing_certificate.p12"
echo "$APPLE_CERTIFICATE" | base64 -d > "$cert_path"
keychain_path="${RUNNER_TEMP}/codex-signing.keychain-db"
security create-keychain -p "$KEYCHAIN_PASSWORD" "$keychain_path"
security set-keychain-settings -lut 21600 "$keychain_path"
security unlock-keychain -p "$KEYCHAIN_PASSWORD" "$keychain_path"
keychain_args=()
cleanup_keychain() {
if ((${#keychain_args[@]} > 0)); then
security list-keychains -s "${keychain_args[@]}" || true
security default-keychain -s "${keychain_args[0]}" || true
else
security list-keychains -s || true
fi
if [[ -f "$keychain_path" ]]; then
security delete-keychain "$keychain_path" || true
fi
}
while IFS= read -r keychain; do
[[ -n "$keychain" ]] && keychain_args+=("$keychain")
done < <(security list-keychains | sed 's/^[[:space:]]*//;s/[[:space:]]*$//;s/"//g')
if ((${#keychain_args[@]} > 0)); then
security list-keychains -s "$keychain_path" "${keychain_args[@]}"
else
security list-keychains -s "$keychain_path"
fi
security default-keychain -s "$keychain_path"
security import "$cert_path" -k "$keychain_path" -P "$APPLE_CERTIFICATE_PASSWORD" -T /usr/bin/codesign -T /usr/bin/security
security set-key-partition-list -S apple-tool:,apple: -s -k "$KEYCHAIN_PASSWORD" "$keychain_path" > /dev/null
codesign_hashes=()
while IFS= read -r hash; do
[[ -n "$hash" ]] && codesign_hashes+=("$hash")
done < <(security find-identity -v -p codesigning "$keychain_path" \
| sed -n 's/.*\([0-9A-F]\{40\}\).*/\1/p' \
| sort -u)
if ((${#codesign_hashes[@]} == 0)); then
echo "No signing identities found in $keychain_path"
cleanup_keychain
rm -f "$cert_path"
exit 1
fi
if ((${#codesign_hashes[@]} > 1)); then
echo "Multiple signing identities found in $keychain_path:"
printf ' %s\n' "${codesign_hashes[@]}"
cleanup_keychain
rm -f "$cert_path"
exit 1
fi
APPLE_CODESIGN_IDENTITY="${codesign_hashes[0]}"
rm -f "$cert_path"
echo "APPLE_CODESIGN_IDENTITY=$APPLE_CODESIGN_IDENTITY" >> "$GITHUB_ENV"
echo "APPLE_CODESIGN_KEYCHAIN=$keychain_path" >> "$GITHUB_ENV"
echo "::add-mask::$APPLE_CODESIGN_IDENTITY"
- name: Sign macOS binaries
if: ${{ inputs.sign-binaries == 'true' }}
shell: bash
run: |
set -euo pipefail
if [[ -z "${APPLE_CODESIGN_IDENTITY:-}" ]]; then
echo "APPLE_CODESIGN_IDENTITY is required for macOS signing"
exit 1
fi
keychain_args=()
if [[ -n "${APPLE_CODESIGN_KEYCHAIN:-}" && -f "${APPLE_CODESIGN_KEYCHAIN}" ]]; then
keychain_args+=(--keychain "${APPLE_CODESIGN_KEYCHAIN}")
fi
for binary in codex codex-responses-api-proxy; do
path="codex-rs/target/${{ inputs.target }}/release/${binary}"
codesign --force --options runtime --timestamp --sign "$APPLE_CODESIGN_IDENTITY" "${keychain_args[@]}" "$path"
done
- name: Notarize macOS binaries
if: ${{ inputs.sign-binaries == 'true' }}
shell: bash
env:
APPLE_NOTARIZATION_KEY_P8: ${{ inputs.apple-notarization-key-p8 }}
APPLE_NOTARIZATION_KEY_ID: ${{ inputs.apple-notarization-key-id }}
APPLE_NOTARIZATION_ISSUER_ID: ${{ inputs.apple-notarization-issuer-id }}
run: |
set -euo pipefail
for var in APPLE_NOTARIZATION_KEY_P8 APPLE_NOTARIZATION_KEY_ID APPLE_NOTARIZATION_ISSUER_ID; do
if [[ -z "${!var:-}" ]]; then
echo "$var is required for notarization"
exit 1
fi
done
notary_key_path="${RUNNER_TEMP}/notarytool.key.p8"
echo "$APPLE_NOTARIZATION_KEY_P8" | base64 -d > "$notary_key_path"
cleanup_notary() {
rm -f "$notary_key_path"
}
trap cleanup_notary EXIT
source "$GITHUB_ACTION_PATH/notary_helpers.sh"
notarize_binary() {
local binary="$1"
local source_path="codex-rs/target/${{ inputs.target }}/release/${binary}"
local archive_path="${RUNNER_TEMP}/${binary}.zip"
if [[ ! -f "$source_path" ]]; then
echo "Binary $source_path not found"
exit 1
fi
rm -f "$archive_path"
ditto -c -k --keepParent "$source_path" "$archive_path"
notarize_submission "$binary" "$archive_path" "$notary_key_path"
}
notarize_binary "codex"
notarize_binary "codex-responses-api-proxy"
- name: Sign and notarize macOS dmg
if: ${{ inputs.sign-dmg == 'true' }}
shell: bash
env:
APPLE_NOTARIZATION_KEY_P8: ${{ inputs.apple-notarization-key-p8 }}
APPLE_NOTARIZATION_KEY_ID: ${{ inputs.apple-notarization-key-id }}
APPLE_NOTARIZATION_ISSUER_ID: ${{ inputs.apple-notarization-issuer-id }}
run: |
set -euo pipefail
for var in APPLE_CODESIGN_IDENTITY APPLE_NOTARIZATION_KEY_P8 APPLE_NOTARIZATION_KEY_ID APPLE_NOTARIZATION_ISSUER_ID; do
if [[ -z "${!var:-}" ]]; then
echo "$var is required"
exit 1
fi
done
notary_key_path="${RUNNER_TEMP}/notarytool.key.p8"
echo "$APPLE_NOTARIZATION_KEY_P8" | base64 -d > "$notary_key_path"
cleanup_notary() {
rm -f "$notary_key_path"
}
trap cleanup_notary EXIT
source "$GITHUB_ACTION_PATH/notary_helpers.sh"
dmg_path="codex-rs/target/${{ inputs.target }}/release/codex-${{ inputs.target }}.dmg"
if [[ ! -f "$dmg_path" ]]; then
echo "dmg $dmg_path not found"
exit 1
fi
keychain_args=()
if [[ -n "${APPLE_CODESIGN_KEYCHAIN:-}" && -f "${APPLE_CODESIGN_KEYCHAIN}" ]]; then
keychain_args+=(--keychain "${APPLE_CODESIGN_KEYCHAIN}")
fi
codesign --force --timestamp --sign "$APPLE_CODESIGN_IDENTITY" "${keychain_args[@]}" "$dmg_path"
notarize_submission "codex-${{ inputs.target }}.dmg" "$dmg_path" "$notary_key_path"
xcrun stapler staple "$dmg_path"
- name: Remove signing keychain
if: ${{ always() }}
shell: bash
env:
APPLE_CODESIGN_KEYCHAIN: ${{ env.APPLE_CODESIGN_KEYCHAIN }}
run: |
set -euo pipefail
if [[ -n "${APPLE_CODESIGN_KEYCHAIN:-}" ]]; then
keychain_args=()
while IFS= read -r keychain; do
[[ "$keychain" == "$APPLE_CODESIGN_KEYCHAIN" ]] && continue
[[ -n "$keychain" ]] && keychain_args+=("$keychain")
done < <(security list-keychains | sed 's/^[[:space:]]*//;s/[[:space:]]*$//;s/"//g')
if ((${#keychain_args[@]} > 0)); then
security list-keychains -s "${keychain_args[@]}"
security default-keychain -s "${keychain_args[0]}"
fi
if [[ -f "$APPLE_CODESIGN_KEYCHAIN" ]]; then
security delete-keychain "$APPLE_CODESIGN_KEYCHAIN"
fi
fi

View File

@@ -0,0 +1,46 @@
#!/usr/bin/env bash
notarize_submission() {
local label="$1"
local path="$2"
local notary_key_path="$3"
if [[ -z "${APPLE_NOTARIZATION_KEY_ID:-}" || -z "${APPLE_NOTARIZATION_ISSUER_ID:-}" ]]; then
echo "APPLE_NOTARIZATION_KEY_ID and APPLE_NOTARIZATION_ISSUER_ID are required for notarization"
exit 1
fi
if [[ -z "$notary_key_path" || ! -f "$notary_key_path" ]]; then
echo "Notary key file $notary_key_path not found"
exit 1
fi
if [[ ! -f "$path" ]]; then
echo "Notarization payload $path not found"
exit 1
fi
local submission_json
submission_json=$(xcrun notarytool submit "$path" \
--key "$notary_key_path" \
--key-id "$APPLE_NOTARIZATION_KEY_ID" \
--issuer "$APPLE_NOTARIZATION_ISSUER_ID" \
--output-format json \
--wait)
local status submission_id
status=$(printf '%s\n' "$submission_json" | jq -r '.status // "Unknown"')
submission_id=$(printf '%s\n' "$submission_json" | jq -r '.id // ""')
if [[ -z "$submission_id" ]]; then
echo "Failed to retrieve submission ID for $label"
exit 1
fi
echo "::notice title=Notarization::$label submission ${submission_id} completed with status ${status}"
if [[ "$status" != "Accepted" ]]; then
echo "Notarization failed for ${label} (submission ${submission_id}, status ${status})"
exit 1
fi
}

View File

@@ -0,0 +1,57 @@
name: windows-code-sign
description: Sign Windows binaries with Azure Trusted Signing.
inputs:
target:
description: Target triple for the artifacts to sign.
required: true
client-id:
description: Azure Trusted Signing client ID.
required: true
tenant-id:
description: Azure tenant ID for Trusted Signing.
required: true
subscription-id:
description: Azure subscription ID for Trusted Signing.
required: true
endpoint:
description: Azure Trusted Signing endpoint.
required: true
account-name:
description: Azure Trusted Signing account name.
required: true
certificate-profile-name:
description: Certificate profile name for signing.
required: true
runs:
using: composite
steps:
- name: Azure login for Trusted Signing (OIDC)
uses: azure/login@v2
with:
client-id: ${{ inputs.client-id }}
tenant-id: ${{ inputs.tenant-id }}
subscription-id: ${{ inputs.subscription-id }}
- name: Sign Windows binaries with Azure Trusted Signing
uses: azure/trusted-signing-action@v0
with:
endpoint: ${{ inputs.endpoint }}
trusted-signing-account-name: ${{ inputs.account-name }}
certificate-profile-name: ${{ inputs.certificate-profile-name }}
exclude-environment-credential: true
exclude-workload-identity-credential: true
exclude-managed-identity-credential: true
exclude-shared-token-cache-credential: true
exclude-visual-studio-credential: true
exclude-visual-studio-code-credential: true
exclude-azure-cli-credential: false
exclude-azure-powershell-credential: true
exclude-azure-developer-cli-credential: true
exclude-interactive-browser-credential: true
cache-dependencies: false
files: |
${{ github.workspace }}/codex-rs/target/${{ inputs.target }}/release/codex.exe
${{ github.workspace }}/codex-rs/target/${{ inputs.target }}/release/codex-responses-api-proxy.exe
${{ github.workspace }}/codex-rs/target/${{ inputs.target }}/release/codex-windows-sandbox-setup.exe
${{ github.workspace }}/codex-rs/target/${{ inputs.target }}/release/codex-command-runner.exe

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.9 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 408 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.1 MiB

After

Width:  |  Height:  |  Size: 818 KiB

View File

@@ -1,3 +1,3 @@
model = "gpt-5"
model = "gpt-5.1"
# Consider setting [mcp_servers] here!

BIN
.github/demo.gif vendored

Binary file not shown.

Before

Width:  |  Height:  |  Size: 19 MiB

View File

@@ -55,6 +55,30 @@
"path": "codex-responses-api-proxy.exe"
}
}
},
"codex-command-runner": {
"platforms": {
"windows-x86_64": {
"regex": "^codex-command-runner-x86_64-pc-windows-msvc\\.exe\\.zst$",
"path": "codex-command-runner.exe"
},
"windows-aarch64": {
"regex": "^codex-command-runner-aarch64-pc-windows-msvc\\.exe\\.zst$",
"path": "codex-command-runner.exe"
}
}
},
"codex-windows-sandbox-setup": {
"platforms": {
"windows-x86_64": {
"regex": "^codex-windows-sandbox-setup-x86_64-pc-windows-msvc\\.exe\\.zst$",
"path": "codex-windows-sandbox-setup.exe"
},
"windows-aarch64": {
"regex": "^codex-windows-sandbox-setup-aarch64-pc-windows-msvc\\.exe\\.zst$",
"path": "codex-windows-sandbox-setup.exe"
}
}
}
}
}

View File

@@ -4,3 +4,5 @@ Before opening this Pull Request, please read the dedicated "Contributing" markd
https://github.com/openai/codex/blob/main/docs/contributing.md
If your PR conforms to our contribution guidelines, replace this text with a detailed and high quality description of your changes.
Include a link to a bug report or enhancement request.

20
.github/workflows/Dockerfile.bazel vendored Normal file
View File

@@ -0,0 +1,20 @@
FROM ubuntu:24.04
# TODO(mbolin): Published to docker.io/mbolin491/codex-bazel:latest for
# initial debugging, but we should publish to a more proper location.
#
# docker buildx create --use
# docker buildx build --platform linux/amd64,linux/arm64 -f .github/workflows/Dockerfile.bazel -t mbolin491/codex-bazel:latest --push .
RUN apt-get update && \
apt-get install -y --no-install-recommends \
curl git python3 ca-certificates && \
rm -rf /var/lib/apt/lists/*
# Install dotslash.
RUN curl -LSfs "https://github.com/facebook/dotslash/releases/download/v0.5.8/dotslash-ubuntu-22.04.$(uname -m).tar.gz" | tar fxz - -C /usr/local/bin
# Ubuntu 24.04 ships with user 'ubuntu' already created with UID 1000.
USER ubuntu
WORKDIR /workspace

110
.github/workflows/bazel.yml vendored Normal file
View File

@@ -0,0 +1,110 @@
name: Bazel (experimental)
# Note this workflow was originally derived from:
# https://github.com/cerisier/toolchains_llvm_bootstrapped/blob/main/.github/workflows/ci.yaml
on:
pull_request: {}
push:
branches:
- main
workflow_dispatch:
concurrency:
# Cancel previous actions from the same PR or branch except 'main' branch.
# See https://docs.github.com/en/actions/using-jobs/using-concurrency and https://docs.github.com/en/actions/learn-github-actions/contexts for more info.
group: concurrency-group::${{ github.workflow }}::${{ github.event.pull_request.number > 0 && format('pr-{0}', github.event.pull_request.number) || github.ref_name }}${{ github.ref_name == 'main' && format('::{0}', github.run_id) || ''}}
cancel-in-progress: ${{ github.ref_name != 'main' }}
jobs:
test:
strategy:
fail-fast: false
matrix:
include:
# macOS
- os: macos-15-xlarge
target: aarch64-apple-darwin
- os: macos-15-xlarge
target: x86_64-apple-darwin
# Linux
- os: ubuntu-24.04-arm
target: aarch64-unknown-linux-gnu
- os: ubuntu-24.04
target: x86_64-unknown-linux-gnu
- os: ubuntu-24.04-arm
target: aarch64-unknown-linux-musl
- os: ubuntu-24.04
target: x86_64-unknown-linux-musl
# TODO: Enable Windows once we fix the toolchain issues there.
#- os: windows-latest
# target: x86_64-pc-windows-gnullvm
runs-on: ${{ matrix.os }}
# Configure a human readable name for each job
name: Local Bazel build on ${{ matrix.os }} for ${{ matrix.target }}
steps:
- uses: actions/checkout@v6
# Some integration tests rely on DotSlash being installed.
# See https://github.com/openai/codex/pull/7617.
- name: Install DotSlash
uses: facebook/install-dotslash@v2
- name: Make DotSlash available in PATH (Unix)
if: runner.os != 'Windows'
run: cp "$(which dotslash)" /usr/local/bin
- name: Make DotSlash available in PATH (Windows)
if: runner.os == 'Windows'
shell: pwsh
run: Copy-Item (Get-Command dotslash).Source -Destination "$env:LOCALAPPDATA\Microsoft\WindowsApps\dotslash.exe"
# Install Bazel via Bazelisk
- name: Set up Bazel
uses: bazelbuild/setup-bazelisk@v3
# TODO(mbolin): Bring this back once we have caching working. Currently,
# we never seem to get a cache hit but we still end up paying the cost of
# uploading at the end of the build, which takes over a minute!
#
# Cache build and external artifacts so that the next ci build is incremental.
# Because github action caches cannot be updated after a build, we need to
# store the contents of each build in a unique cache key, then fall back to loading
# it on the next ci run. We use hashFiles(...) in the key and restore-keys- with
# the prefix to load the most recent cache for the branch on a cache miss. You
# should customize the contents of hashFiles to capture any bazel input sources,
# although this doesn't need to be perfect. If none of the input sources change
# then a cache hit will load an existing cache and bazel won't have to do any work.
# In the case of a cache miss, you want the fallback cache to contain most of the
# previously built artifacts to minimize build time. The more precise you are with
# hashFiles sources the less work bazel will have to do.
# - name: Mount bazel caches
# uses: actions/cache@v4
# with:
# path: |
# ~/.cache/bazel-repo-cache
# ~/.cache/bazel-repo-contents-cache
# key: bazel-cache-${{ matrix.os }}-${{ hashFiles('**/BUILD.bazel', '**/*.bzl', 'MODULE.bazel') }}
# restore-keys: |
# bazel-cache-${{ matrix.os }}
- name: Configure Bazel startup args (Windows)
if: runner.os == 'Windows'
shell: pwsh
run: |
# Use a very short path to reduce argv/path length issues.
"BAZEL_STARTUP_ARGS=--output_user_root=C:\" | Out-File -FilePath $env:GITHUB_ENV -Encoding utf8 -Append
- name: bazel test //...
env:
BUILDBUDDY_API_KEY: ${{ secrets.BUILDBUDDY_API_KEY }}
shell: bash
run: |
bazel $BAZEL_STARTUP_ARGS --bazelrc=.github/workflows/ci.bazelrc test //... \
--build_metadata=REPO_URL=https://github.com/openai/codex.git \
--build_metadata=COMMIT_SHA=$(git rev-parse HEAD) \
--build_metadata=ROLE=CI \
--build_metadata=VISIBILITY=PUBLIC \
"--remote_header=x-buildbuddy-api-key=$BUILDBUDDY_API_KEY"

26
.github/workflows/cargo-deny.yml vendored Normal file
View File

@@ -0,0 +1,26 @@
name: cargo-deny
on:
pull_request:
push:
branches:
- main
jobs:
cargo-deny:
runs-on: ubuntu-latest
defaults:
run:
working-directory: ./codex-rs
steps:
- name: Checkout
uses: actions/checkout@v6
- name: Install Rust toolchain
uses: dtolnay/rust-toolchain@stable
- name: Run cargo-deny
uses: EmbarkStudios/cargo-deny-action@v2
with:
rust-version: stable
manifest-path: ./codex-rs/Cargo.toml

20
.github/workflows/ci.bazelrc vendored Normal file
View File

@@ -0,0 +1,20 @@
common --remote_download_minimal
common --nobuild_runfile_links
common --keep_going
# We prefer to run the build actions entirely remotely so we can dial up the concurrency.
# We have platform-specific tests, so we want to execute the tests on all platforms using the strongest sandboxing available on each platform.
# On linux, we can do a full remote build/test, by targeting the right (x86/arm) runners, so we have coverage of both.
# Linux crossbuilds don't work until we untangle the libc constraint mess.
common:linux --config=remote
common:linux --strategy=remote
common:linux --platforms=//:rbe
# On mac, we can run all the build actions remotely but test actions locally.
common:macos --config=remote
common:macos --strategy=remote
common:macos --strategy=TestRunner=darwin-sandbox,local
common:windows --strategy=TestRunner=local

View File

@@ -12,7 +12,7 @@ jobs:
NODE_OPTIONS: --max-old-space-size=4096
steps:
- name: Checkout repository
uses: actions/checkout@v5
uses: actions/checkout@v6
- name: Setup pnpm
uses: pnpm/action-setup@v4
@@ -20,7 +20,7 @@ jobs:
run_install: false
- name: Setup Node.js
uses: actions/setup-node@v5
uses: actions/setup-node@v6
with:
node-version: 22
@@ -36,7 +36,8 @@ jobs:
GH_TOKEN: ${{ github.token }}
run: |
set -euo pipefail
CODEX_VERSION=0.40.0
# Use a rust-release version that includes all native binaries.
CODEX_VERSION=0.74.0
OUTPUT_DIR="${RUNNER_TEMP}"
python3 ./scripts/stage_npm_packages.py \
--release-version "$CODEX_VERSION" \
@@ -46,7 +47,7 @@ jobs:
echo "pack_output=$PACK_OUTPUT" >> "$GITHUB_OUTPUT"
- name: Upload staged npm package artifact
uses: actions/upload-artifact@v4
uses: actions/upload-artifact@v6
with:
name: codex-npm-staging
path: ${{ steps.stage_npm_package.outputs.pack_output }}

View File

@@ -13,17 +13,37 @@ permissions:
jobs:
cla:
# Only run the CLA assistant for the canonical openai repo so forks are not blocked
# and contributors who signed previously do not receive duplicate CLA notifications.
if: ${{ github.repository_owner == 'openai' }}
runs-on: ubuntu-latest
steps:
- uses: contributor-assistant/github-action@v2.6.1
# Run on close only if the PR was merged. This will lock the PR to preserve
# the CLA agreement. We don't want to lock PRs that have been closed without
# merging because the contributor may want to respond with additional comments.
# This action has a "lock-pullrequest-aftermerge" option that can be set to false,
# but that would unconditionally skip locking even in cases where the PR was merged.
if: |
github.event_name == 'pull_request_target' ||
github.event.comment.body == 'recheck' ||
github.event.comment.body == 'I have read the CLA Document and I hereby sign the CLA'
(
github.event_name == 'pull_request_target' &&
(
github.event.action == 'opened' ||
github.event.action == 'synchronize' ||
(github.event.action == 'closed' && github.event.pull_request.merged == true)
)
) ||
(
github.event_name == 'issue_comment' &&
(
github.event.comment.body == 'recheck' ||
github.event.comment.body == 'I have read the CLA Document and I hereby sign the CLA'
)
)
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
with:
path-to-document: https://github.com/openai/codex/blob/main/docs/CLA.md
path-to-signatures: signatures/cla.json
branch: cla-signatures
allowlist: dependabot[bot]
allowlist: codex,dependabot,dependabot[bot],github-actions[bot]

View File

@@ -0,0 +1,107 @@
name: Close stale contributor PRs
on:
workflow_dispatch:
schedule:
- cron: "0 6 * * *"
permissions:
contents: read
issues: write
pull-requests: write
jobs:
close-stale-contributor-prs:
# Prevent scheduled runs on forks
if: github.repository == 'openai/codex'
runs-on: ubuntu-latest
steps:
- name: Close inactive PRs from contributors
uses: actions/github-script@v8
with:
github-token: ${{ secrets.GITHUB_TOKEN }}
script: |
const DAYS_INACTIVE = 14;
const cutoff = new Date(Date.now() - DAYS_INACTIVE * 24 * 60 * 60 * 1000);
const { owner, repo } = context.repo;
const dryRun = false;
const stalePrs = [];
core.info(`Dry run mode: ${dryRun}`);
const prs = await github.paginate(github.rest.pulls.list, {
owner,
repo,
state: "open",
per_page: 100,
sort: "updated",
direction: "asc",
});
for (const pr of prs) {
const lastUpdated = new Date(pr.updated_at);
if (lastUpdated > cutoff) {
core.info(`PR ${pr.number} is fresh`);
continue;
}
if (!pr.user || pr.user.type !== "User") {
core.info(`PR ${pr.number} wasn't created by a user`);
continue;
}
let permission;
try {
const permissionResponse = await github.rest.repos.getCollaboratorPermissionLevel({
owner,
repo,
username: pr.user.login,
});
permission = permissionResponse.data.permission;
} catch (error) {
if (error.status === 404) {
core.info(`Author ${pr.user.login} is not a collaborator; skipping #${pr.number}`);
continue;
}
throw error;
}
const hasContributorAccess = ["admin", "maintain", "write"].includes(permission);
if (!hasContributorAccess) {
core.info(`Author ${pr.user.login} has ${permission} access; skipping #${pr.number}`);
continue;
}
stalePrs.push(pr);
}
if (!stalePrs.length) {
core.info("No stale contributor pull requests found.");
return;
}
for (const pr of stalePrs) {
const issue_number = pr.number;
const closeComment = `Closing this pull request because it has had no updates for more than ${DAYS_INACTIVE} days. If you plan to continue working on it, feel free to reopen or open a new PR.`;
if (dryRun) {
core.info(`[dry-run] Would close contributor PR #${issue_number} from ${pr.user.login}`);
continue;
}
await github.rest.issues.createComment({
owner,
repo,
issue_number,
body: closeComment,
});
await github.rest.pulls.update({
owner,
repo,
pull_number: issue_number,
state: "closed",
});
core.info(`Closed contributor PR #${issue_number} from ${pr.user.login}`);
}

View File

@@ -18,10 +18,10 @@ jobs:
steps:
- name: Checkout
uses: actions/checkout@v5
uses: actions/checkout@v6
- name: Annotate locations with typos
uses: codespell-project/codespell-problem-matcher@b80729f885d32f78a716c2f107b4db1025001c42 # v1
- name: Codespell
uses: codespell-project/actions-codespell@406322ec52dd7b488e48c1c4b82e2a8b3a1bf630 # v2.1
uses: codespell-project/actions-codespell@8f01853be192eb0f849a5c7d721450e7a467c579 # v2.2
with:
ignore_words_file: .codespellignore

View File

@@ -9,14 +9,15 @@ on:
jobs:
gather-duplicates:
name: Identify potential duplicates
if: ${{ github.event.action == 'opened' || (github.event.action == 'labeled' && github.event.label.name == 'codex-deduplicate') }}
# Prevent runs on forks (requires OpenAI API key, wastes Actions minutes)
if: github.repository == 'openai/codex' && (github.event.action == 'opened' || (github.event.action == 'labeled' && github.event.label.name == 'codex-deduplicate'))
runs-on: ubuntu-latest
permissions:
contents: read
outputs:
codex_output: ${{ steps.codex.outputs.final-message }}
steps:
- uses: actions/checkout@v4
- uses: actions/checkout@v6
- name: Prepare Codex inputs
env:
@@ -46,7 +47,6 @@ jobs:
with:
openai-api-key: ${{ secrets.CODEX_OPENAI_API_KEY }}
allow-users: "*"
model: gpt-5
prompt: |
You are an assistant that triages new GitHub issues by identifying potential duplicates.
@@ -87,7 +87,7 @@ jobs:
issues: write
steps:
- name: Comment on issue
uses: actions/github-script@v7
uses: actions/github-script@v8
env:
CODEX_OUTPUT: ${{ needs.gather-duplicates.outputs.codex_output }}
with:

View File

@@ -9,14 +9,15 @@ on:
jobs:
gather-labels:
name: Generate label suggestions
if: ${{ github.event.action == 'opened' || (github.event.action == 'labeled' && github.event.label.name == 'codex-label') }}
# Prevent runs on forks (requires OpenAI API key, wastes Actions minutes)
if: github.repository == 'openai/codex' && (github.event.action == 'opened' || (github.event.action == 'labeled' && github.event.label.name == 'codex-label'))
runs-on: ubuntu-latest
permissions:
contents: read
outputs:
codex_output: ${{ steps.codex.outputs.final-message }}
steps:
- uses: actions/checkout@v4
- uses: actions/checkout@v6
- id: codex
uses: openai/codex-action@main
@@ -26,21 +27,36 @@ jobs:
prompt: |
You are an assistant that reviews GitHub issues for the repository.
Your job is to choose the most appropriate existing labels for the issue described later in this prompt.
Your job is to choose the most appropriate labels for the issue described later in this prompt.
Follow these rules:
- Only pick labels out of the list below.
- Prefer a small set of precise labels over many broad ones.
Labels to apply:
- Add one (and only one) of the following three labels to distinguish the type of issue. Default to "bug" if unsure.
1. bug — Reproducible defects in Codex products (CLI, VS Code extension, web, auth).
2. enhancement — Feature requests or usability improvements that ask for new capabilities, better ergonomics, or quality-of-life tweaks.
3. extension — VS Code (or other IDE) extension-specific issues.
4. windows-os — Bugs or friction specific to Windows environments (always when PowerShell is mentioned, path handling, copy/paste, OS-specific auth or tooling failures).
5. mcp — Topics involving Model Context Protocol servers/clients.
6. codex-web — Issues targeting the Codex web UI/Cloud experience.
8. azure — Problems or requests tied to Azure OpenAI deployments.
9. documentation — Updates or corrections needed in docs/README/config references (broken links, missing examples, outdated keys, clarification requests).
10. model-behaviorUndesirable LLM behavior: forgetting goals, refusing work, hallucinating environment details, quota misreports, or other reasoning/performance anomalies.
3. documentation — Updates or corrections needed in docs/README/config references (broken links, missing examples, outdated keys, clarification requests).
- If applicable, add one of the following labels to specify which sub-product or product surface the issue relates to.
1. CLI — the Codex command line interface.
2. extension — VS Code (or other IDE) extension-specific issues.
3. codex-web — Issues targeting the Codex web UI/Cloud experience.
4. github-actionIssues with the Codex GitHub action.
5. iOS — Issues with the Codex iOS app.
- Additionally add zero or more of the following labels that are relevant to the issue content. Prefer a small set of precise labels over many broad ones.
1. windows-os — Bugs or friction specific to Windows environments (always when PowerShell is mentioned, path handling, copy/paste, OS-specific auth or tooling failures).
2. mcp — Topics involving Model Context Protocol servers/clients.
3. mcp-server — Problems related to the codex mcp-server command, where codex runs as an MCP server.
4. azure — Problems or requests tied to Azure OpenAI deployments.
5. model-behavior — Undesirable LLM behavior: forgetting goals, refusing work, hallucinating environment details, quota misreports, or other reasoning/performance anomalies.
6. code-review — Issues related to the code review feature or functionality.
7. auth - Problems related to authentication, login, or access tokens.
8. codex-exec - Problems related to the "codex exec" command or functionality.
9. context-management - Problems related to compaction, context windows, or available context reporting.
10. custom-model - Problems that involve using custom model providers, local models, or OSS models.
11. rate-limits - Problems related to token limits, rate limits, or token usage reporting.
12. sandbox - Issues related to local sandbox environments or tool call approvals to override sandbox restrictions.
13. tool-calls - Problems related to specific tool call invocations including unexpected errors, failures, or hangs.
14. TUI - Problems with the terminal user interface (TUI) including keyboard shortcuts, copy & pasting, menus, or screen update issues.
Issue number: ${{ github.event.issue.number }}

View File

@@ -9,7 +9,7 @@ on:
# CI builds in debug (dev) for faster signal.
jobs:
# --- Detect what changed (always runs) -------------------------------------
# --- Detect what changed to detect which tests to run (always runs) -------------------------------------
changed:
name: Detect changed areas
runs-on: ubuntu-24.04
@@ -17,7 +17,7 @@ jobs:
codex: ${{ steps.detect.outputs.codex }}
workflows: ${{ steps.detect.outputs.workflows }}
steps:
- uses: actions/checkout@v5
- uses: actions/checkout@v6
with:
fetch-depth: 0
- name: Detect changed paths (no external action)
@@ -28,9 +28,11 @@ jobs:
if [[ "${{ github.event_name }}" == "pull_request" ]]; then
BASE_SHA='${{ github.event.pull_request.base.sha }}'
HEAD_SHA='${{ github.event.pull_request.head.sha }}'
echo "Base SHA: $BASE_SHA"
# List files changed between base and current HEAD (merge-base aware)
mapfile -t files < <(git diff --name-only --no-renames "$BASE_SHA"...HEAD)
echo "Head SHA: $HEAD_SHA"
# List files changed between base and PR head
mapfile -t files < <(git diff --name-only --no-renames "$BASE_SHA" "$HEAD_SHA")
else
# On push / manual runs, default to running everything
files=("codex-rs/force" ".github/force")
@@ -56,7 +58,7 @@ jobs:
run:
working-directory: codex-rs
steps:
- uses: actions/checkout@v5
- uses: actions/checkout@v6
- uses: dtolnay/rust-toolchain@1.90
with:
components: rustfmt
@@ -74,9 +76,9 @@ jobs:
run:
working-directory: codex-rs
steps:
- uses: actions/checkout@v5
- uses: actions/checkout@v6
- uses: dtolnay/rust-toolchain@1.90
- uses: taiki-e/install-action@0c5db7f7f897c03b771660e91d065338615679f4 # v2
- uses: taiki-e/install-action@44c6d64aa62cd779e873306675c7a58e86d6d532 # v2
with:
tool: cargo-shear
version: 1.5.1
@@ -84,8 +86,8 @@ jobs:
run: cargo shear
# --- CI to validate on different os/targets --------------------------------
lint_build_test:
name: ${{ matrix.runner }} - ${{ matrix.target }}${{ matrix.profile == 'release' && ' (release)' || '' }}
lint_build:
name: Lint/Build — ${{ matrix.runner }} - ${{ matrix.target }}${{ matrix.profile == 'release' && ' (release)' || '' }}
runs-on: ${{ matrix.runner }}
timeout-minutes: 30
needs: changed
@@ -94,6 +96,11 @@ jobs:
defaults:
run:
working-directory: codex-rs
env:
# Speed up repeated builds across CI runs by caching compiled objects (non-Windows).
USE_SCCACHE: ${{ startsWith(matrix.runner, 'windows') && 'false' || 'true' }}
CARGO_INCREMENTAL: "0"
SCCACHE_CACHE_SIZE: 10G
strategy:
fail-fast: false
@@ -142,37 +149,116 @@ jobs:
profile: release
steps:
- uses: actions/checkout@v5
- uses: actions/checkout@v6
- uses: dtolnay/rust-toolchain@1.90
with:
targets: ${{ matrix.target }}
components: clippy
- name: Compute lockfile hash
id: lockhash
working-directory: codex-rs
shell: bash
run: |
set -euo pipefail
echo "hash=$(sha256sum Cargo.lock | cut -d' ' -f1)" >> "$GITHUB_OUTPUT"
echo "toolchain_hash=$(sha256sum rust-toolchain.toml | cut -d' ' -f1)" >> "$GITHUB_OUTPUT"
# Explicit cache restore: split cargo home vs target, so we can
# avoid caching the large target dir on the gnu-dev job.
- name: Restore cargo home cache
id: cache_cargo_home_restore
uses: actions/cache/restore@v4
uses: actions/cache/restore@v5
with:
path: |
~/.cargo/bin/
~/.cargo/registry/index/
~/.cargo/registry/cache/
~/.cargo/git/db/
key: cargo-home-${{ matrix.runner }}-${{ matrix.target }}-${{ matrix.profile }}-${{ hashFiles('**/Cargo.lock') }}
key: cargo-home-${{ matrix.runner }}-${{ matrix.target }}-${{ matrix.profile }}-${{ steps.lockhash.outputs.hash }}-${{ steps.lockhash.outputs.toolchain_hash }}
restore-keys: |
cargo-home-${{ matrix.runner }}-${{ matrix.target }}-${{ matrix.profile }}-
- name: Restore target cache (except gnu-dev)
id: cache_target_restore
if: ${{ !(matrix.target == 'x86_64-unknown-linux-gnu' && matrix.profile != 'release') }}
uses: actions/cache/restore@v4
# Install and restore sccache cache
- name: Install sccache
if: ${{ env.USE_SCCACHE == 'true' }}
uses: taiki-e/install-action@44c6d64aa62cd779e873306675c7a58e86d6d532 # v2
with:
path: ${{ github.workspace }}/codex-rs/target/
key: cargo-target-${{ matrix.runner }}-${{ matrix.target }}-${{ matrix.profile }}-${{ hashFiles('**/Cargo.lock') }}
tool: sccache
version: 0.7.5
- name: Configure sccache backend
if: ${{ env.USE_SCCACHE == 'true' }}
shell: bash
run: |
set -euo pipefail
if [[ -n "${ACTIONS_CACHE_URL:-}" && -n "${ACTIONS_RUNTIME_TOKEN:-}" ]]; then
echo "SCCACHE_GHA_ENABLED=true" >> "$GITHUB_ENV"
echo "Using sccache GitHub backend"
else
echo "SCCACHE_GHA_ENABLED=false" >> "$GITHUB_ENV"
echo "SCCACHE_DIR=${{ github.workspace }}/.sccache" >> "$GITHUB_ENV"
echo "Using sccache local disk + actions/cache fallback"
fi
- name: Enable sccache wrapper
if: ${{ env.USE_SCCACHE == 'true' }}
shell: bash
run: echo "RUSTC_WRAPPER=sccache" >> "$GITHUB_ENV"
- name: Restore sccache cache (fallback)
if: ${{ env.USE_SCCACHE == 'true' && env.SCCACHE_GHA_ENABLED != 'true' }}
id: cache_sccache_restore
uses: actions/cache/restore@v5
with:
path: ${{ github.workspace }}/.sccache/
key: sccache-${{ matrix.runner }}-${{ matrix.target }}-${{ matrix.profile }}-${{ steps.lockhash.outputs.hash }}-${{ github.run_id }}
restore-keys: |
sccache-${{ matrix.runner }}-${{ matrix.target }}-${{ matrix.profile }}-${{ steps.lockhash.outputs.hash }}-
sccache-${{ matrix.runner }}-${{ matrix.target }}-${{ matrix.profile }}-
- if: ${{ matrix.target == 'x86_64-unknown-linux-musl' || matrix.target == 'aarch64-unknown-linux-musl'}}
name: Prepare APT cache directories (musl)
shell: bash
run: |
set -euo pipefail
sudo mkdir -p /var/cache/apt/archives /var/lib/apt/lists
sudo chown -R "$USER:$USER" /var/cache/apt /var/lib/apt/lists
- if: ${{ matrix.target == 'x86_64-unknown-linux-musl' || matrix.target == 'aarch64-unknown-linux-musl'}}
name: Restore APT cache (musl)
id: cache_apt_restore
uses: actions/cache/restore@v5
with:
path: |
/var/cache/apt
key: apt-${{ matrix.runner }}-${{ matrix.target }}-v1
- if: ${{ matrix.target == 'x86_64-unknown-linux-musl' || matrix.target == 'aarch64-unknown-linux-musl'}}
name: Install musl build tools
env:
DEBIAN_FRONTEND: noninteractive
shell: bash
run: |
sudo apt install -y musl-tools pkg-config && sudo rm -rf /var/lib/apt/lists/*
set -euo pipefail
sudo apt-get -y update -o Acquire::Retries=3
sudo apt-get -y install --no-install-recommends musl-tools pkg-config
- name: Install cargo-chef
if: ${{ matrix.profile == 'release' }}
uses: taiki-e/install-action@44c6d64aa62cd779e873306675c7a58e86d6d532 # v2
with:
tool: cargo-chef
version: 0.1.71
- name: Pre-warm dependency cache (cargo-chef)
if: ${{ matrix.profile == 'release' }}
shell: bash
run: |
set -euo pipefail
RECIPE="${RUNNER_TEMP}/chef-recipe.json"
cargo chef prepare --recipe-path "$RECIPE"
cargo chef cook --recipe-path "$RECIPE" --target ${{ matrix.target }} --release --all-features
- name: cargo clippy
id: clippy
@@ -191,59 +277,243 @@ jobs:
find . -name Cargo.toml -mindepth 2 -maxdepth 2 -print0 \
| xargs -0 -n1 -I{} bash -c 'cd "$(dirname "{}")" && cargo check --profile ${{ matrix.profile }}'
- uses: taiki-e/install-action@0c5db7f7f897c03b771660e91d065338615679f4 # v2
with:
tool: nextest
version: 0.9.103
- name: tests
id: test
# Tests take too long for release builds to run them on every PR.
if: ${{ matrix.profile != 'release' }}
continue-on-error: true
run: cargo nextest run --all-features --no-fail-fast --target ${{ matrix.target }} --cargo-profile ci-test
env:
RUST_BACKTRACE: 1
# Save caches explicitly; make non-fatal so cache packaging
# never fails the overall job. Only save when key wasn't hit.
- name: Save cargo home cache
if: always() && !cancelled() && steps.cache_cargo_home_restore.outputs.cache-hit != 'true'
continue-on-error: true
uses: actions/cache/save@v4
uses: actions/cache/save@v5
with:
path: |
~/.cargo/bin/
~/.cargo/registry/index/
~/.cargo/registry/cache/
~/.cargo/git/db/
key: cargo-home-${{ matrix.runner }}-${{ matrix.target }}-${{ matrix.profile }}-${{ hashFiles('**/Cargo.lock') }}
key: cargo-home-${{ matrix.runner }}-${{ matrix.target }}-${{ matrix.profile }}-${{ steps.lockhash.outputs.hash }}-${{ steps.lockhash.outputs.toolchain_hash }}
- name: Save target cache (except gnu-dev)
if: >-
always() && !cancelled() &&
(steps.cache_target_restore.outputs.cache-hit != 'true') &&
!(matrix.target == 'x86_64-unknown-linux-gnu' && matrix.profile != 'release')
- name: Save sccache cache (fallback)
if: always() && !cancelled() && env.USE_SCCACHE == 'true' && env.SCCACHE_GHA_ENABLED != 'true'
continue-on-error: true
uses: actions/cache/save@v4
uses: actions/cache/save@v5
with:
path: ${{ github.workspace }}/codex-rs/target/
key: cargo-target-${{ matrix.runner }}-${{ matrix.target }}-${{ matrix.profile }}-${{ hashFiles('**/Cargo.lock') }}
path: ${{ github.workspace }}/.sccache/
key: sccache-${{ matrix.runner }}-${{ matrix.target }}-${{ matrix.profile }}-${{ steps.lockhash.outputs.hash }}-${{ github.run_id }}
- name: sccache stats
if: always() && env.USE_SCCACHE == 'true'
continue-on-error: true
run: sccache --show-stats || true
- name: sccache summary
if: always() && env.USE_SCCACHE == 'true'
shell: bash
run: |
{
echo "### sccache stats — ${{ matrix.target }} (${{ matrix.profile }})";
echo;
echo '```';
sccache --show-stats || true;
echo '```';
} >> "$GITHUB_STEP_SUMMARY"
- name: Save APT cache (musl)
if: always() && !cancelled() && (matrix.target == 'x86_64-unknown-linux-musl' || matrix.target == 'aarch64-unknown-linux-musl') && steps.cache_apt_restore.outputs.cache-hit != 'true'
continue-on-error: true
uses: actions/cache/save@v5
with:
path: |
/var/cache/apt
key: apt-${{ matrix.runner }}-${{ matrix.target }}-v1
# Fail the job if any of the previous steps failed.
- name: verify all steps passed
if: |
steps.clippy.outcome == 'failure' ||
steps.cargo_check_all_crates.outcome == 'failure' ||
steps.test.outcome == 'failure'
steps.cargo_check_all_crates.outcome == 'failure'
run: |
echo "One or more checks failed (clippy, cargo_check_all_crates, or test). See logs for details."
echo "One or more checks failed (clippy or cargo_check_all_crates). See logs for details."
exit 1
tests:
name: Tests — ${{ matrix.runner }} - ${{ matrix.target }}
runs-on: ${{ matrix.runner }}
timeout-minutes: 30
needs: changed
if: ${{ needs.changed.outputs.codex == 'true' || needs.changed.outputs.workflows == 'true' || github.event_name == 'push' }}
defaults:
run:
working-directory: codex-rs
env:
# Speed up repeated builds across CI runs by caching compiled objects (non-Windows).
USE_SCCACHE: ${{ startsWith(matrix.runner, 'windows') && 'false' || 'true' }}
CARGO_INCREMENTAL: "0"
SCCACHE_CACHE_SIZE: 10G
strategy:
fail-fast: false
matrix:
include:
- runner: macos-14
target: aarch64-apple-darwin
profile: dev
- runner: ubuntu-24.04
target: x86_64-unknown-linux-gnu
profile: dev
- runner: ubuntu-24.04-arm
target: aarch64-unknown-linux-gnu
profile: dev
- runner: windows-latest
target: x86_64-pc-windows-msvc
profile: dev
- runner: windows-11-arm
target: aarch64-pc-windows-msvc
profile: dev
steps:
- uses: actions/checkout@v6
# We have been running out of space when running this job on Linux for
# x86_64-unknown-linux-gnu, so remove some unnecessary dependencies.
- name: Remove unnecessary dependencies to save space
if: ${{ startsWith(matrix.runner, 'ubuntu') }}
shell: bash
run: |
set -euo pipefail
sudo rm -rf \
/usr/local/lib/android \
/usr/share/dotnet \
/usr/local/share/boost \
/usr/local/lib/node_modules \
/opt/ghc
sudo apt-get remove -y docker.io docker-compose podman buildah
# Some integration tests rely on DotSlash being installed.
# See https://github.com/openai/codex/pull/7617.
- name: Install DotSlash
uses: facebook/install-dotslash@v2
- uses: dtolnay/rust-toolchain@1.90
with:
targets: ${{ matrix.target }}
- name: Compute lockfile hash
id: lockhash
working-directory: codex-rs
shell: bash
run: |
set -euo pipefail
echo "hash=$(sha256sum Cargo.lock | cut -d' ' -f1)" >> "$GITHUB_OUTPUT"
echo "toolchain_hash=$(sha256sum rust-toolchain.toml | cut -d' ' -f1)" >> "$GITHUB_OUTPUT"
- name: Restore cargo home cache
id: cache_cargo_home_restore
uses: actions/cache/restore@v5
with:
path: |
~/.cargo/bin/
~/.cargo/registry/index/
~/.cargo/registry/cache/
~/.cargo/git/db/
key: cargo-home-${{ matrix.runner }}-${{ matrix.target }}-${{ matrix.profile }}-${{ steps.lockhash.outputs.hash }}-${{ steps.lockhash.outputs.toolchain_hash }}
restore-keys: |
cargo-home-${{ matrix.runner }}-${{ matrix.target }}-${{ matrix.profile }}-
- name: Install sccache
if: ${{ env.USE_SCCACHE == 'true' }}
uses: taiki-e/install-action@44c6d64aa62cd779e873306675c7a58e86d6d532 # v2
with:
tool: sccache
version: 0.7.5
- name: Configure sccache backend
if: ${{ env.USE_SCCACHE == 'true' }}
shell: bash
run: |
set -euo pipefail
if [[ -n "${ACTIONS_CACHE_URL:-}" && -n "${ACTIONS_RUNTIME_TOKEN:-}" ]]; then
echo "SCCACHE_GHA_ENABLED=true" >> "$GITHUB_ENV"
echo "Using sccache GitHub backend"
else
echo "SCCACHE_GHA_ENABLED=false" >> "$GITHUB_ENV"
echo "SCCACHE_DIR=${{ github.workspace }}/.sccache" >> "$GITHUB_ENV"
echo "Using sccache local disk + actions/cache fallback"
fi
- name: Enable sccache wrapper
if: ${{ env.USE_SCCACHE == 'true' }}
shell: bash
run: echo "RUSTC_WRAPPER=sccache" >> "$GITHUB_ENV"
- name: Restore sccache cache (fallback)
if: ${{ env.USE_SCCACHE == 'true' && env.SCCACHE_GHA_ENABLED != 'true' }}
id: cache_sccache_restore
uses: actions/cache/restore@v5
with:
path: ${{ github.workspace }}/.sccache/
key: sccache-${{ matrix.runner }}-${{ matrix.target }}-${{ matrix.profile }}-${{ steps.lockhash.outputs.hash }}-${{ github.run_id }}
restore-keys: |
sccache-${{ matrix.runner }}-${{ matrix.target }}-${{ matrix.profile }}-${{ steps.lockhash.outputs.hash }}-
sccache-${{ matrix.runner }}-${{ matrix.target }}-${{ matrix.profile }}-
- uses: taiki-e/install-action@44c6d64aa62cd779e873306675c7a58e86d6d532 # v2
with:
tool: nextest
version: 0.9.103
- name: tests
id: test
run: cargo nextest run --all-features --no-fail-fast --target ${{ matrix.target }} --cargo-profile ci-test
env:
RUST_BACKTRACE: 1
NEXTEST_STATUS_LEVEL: leak
- name: Save cargo home cache
if: always() && !cancelled() && steps.cache_cargo_home_restore.outputs.cache-hit != 'true'
continue-on-error: true
uses: actions/cache/save@v5
with:
path: |
~/.cargo/bin/
~/.cargo/registry/index/
~/.cargo/registry/cache/
~/.cargo/git/db/
key: cargo-home-${{ matrix.runner }}-${{ matrix.target }}-${{ matrix.profile }}-${{ steps.lockhash.outputs.hash }}-${{ steps.lockhash.outputs.toolchain_hash }}
- name: Save sccache cache (fallback)
if: always() && !cancelled() && env.USE_SCCACHE == 'true' && env.SCCACHE_GHA_ENABLED != 'true'
continue-on-error: true
uses: actions/cache/save@v5
with:
path: ${{ github.workspace }}/.sccache/
key: sccache-${{ matrix.runner }}-${{ matrix.target }}-${{ matrix.profile }}-${{ steps.lockhash.outputs.hash }}-${{ github.run_id }}
- name: sccache stats
if: always() && env.USE_SCCACHE == 'true'
continue-on-error: true
run: sccache --show-stats || true
- name: sccache summary
if: always() && env.USE_SCCACHE == 'true'
shell: bash
run: |
{
echo "### sccache stats — ${{ matrix.target }} (tests)";
echo;
echo '```';
sccache --show-stats || true;
echo '```';
} >> "$GITHUB_STEP_SUMMARY"
- name: verify tests passed
if: steps.test.outcome == 'failure'
run: |
echo "Tests failed. See logs for details."
exit 1
# --- Gatherer job that you mark as the ONLY required status -----------------
results:
name: CI results (required)
needs: [changed, general, cargo_shear, lint_build_test]
needs: [changed, general, cargo_shear, lint_build, tests]
if: always()
runs-on: ubuntu-24.04
steps:
@@ -252,7 +522,8 @@ jobs:
run: |
echo "general: ${{ needs.general.result }}"
echo "shear : ${{ needs.cargo_shear.result }}"
echo "matrix : ${{ needs.lint_build_test.result }}"
echo "lint : ${{ needs.lint_build.result }}"
echo "tests : ${{ needs.tests.result }}"
# If nothing relevant changed (PR touching only root README, etc.),
# declare success regardless of other jobs.
@@ -264,4 +535,10 @@ jobs:
# Otherwise require the jobs to have succeeded
[[ '${{ needs.general.result }}' == 'success' ]] || { echo 'general failed'; exit 1; }
[[ '${{ needs.cargo_shear.result }}' == 'success' ]] || { echo 'cargo_shear failed'; exit 1; }
[[ '${{ needs.lint_build_test.result }}' == 'success' ]] || { echo 'matrix failed'; exit 1; }
[[ '${{ needs.lint_build.result }}' == 'success' ]] || { echo 'lint_build failed'; exit 1; }
[[ '${{ needs.tests.result }}' == 'success' ]] || { echo 'tests failed'; exit 1; }
- name: sccache summary note
if: always()
run: |
echo "Per-job sccache stats are attached to each matrix job's Step Summary."

View File

@@ -0,0 +1,53 @@
name: rust-release-prepare
on:
workflow_dispatch:
schedule:
- cron: "0 */4 * * *"
concurrency:
group: ${{ github.workflow }}
cancel-in-progress: false
permissions:
contents: write
pull-requests: write
jobs:
prepare:
# Prevent scheduled runs on forks (no secrets, wastes Actions minutes)
if: github.repository == 'openai/codex'
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v6
with:
ref: main
fetch-depth: 0
- name: Update models.json
env:
OPENAI_API_KEY: ${{ secrets.CODEX_OPENAI_API_KEY }}
run: |
set -euo pipefail
client_version="99.99.99"
terminal_info="github-actions"
user_agent="codex_cli_rs/99.99.99 (Linux $(uname -r); $(uname -m)) ${terminal_info}"
base_url="${OPENAI_BASE_URL:-https://chatgpt.com/backend-api/codex}"
headers=(
-H "Authorization: Bearer ${OPENAI_API_KEY}"
-H "User-Agent: ${user_agent}"
)
url="${base_url%/}/models?client_version=${client_version}"
curl --http1.1 --fail --show-error --location "${headers[@]}" "${url}" | jq '.' > codex-rs/core/models.json
- name: Open pull request (if changed)
uses: peter-evans/create-pull-request@v8
with:
commit-message: "Update models.json"
title: "Update models.json"
body: "Automated update of models.json."
branch: "bot/update-models-json"
reviewers: "pakrym-oai,aibrahim-oai"
delete-branch: true

View File

@@ -19,7 +19,7 @@ jobs:
tag-check:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v5
- uses: actions/checkout@v6
- name: Validate tag matches Cargo.toml version
shell: bash
@@ -50,6 +50,9 @@ jobs:
name: Build - ${{ matrix.runner }} - ${{ matrix.target }}
runs-on: ${{ matrix.runner }}
timeout-minutes: 30
permissions:
contents: read
id-token: write
defaults:
run:
working-directory: codex-rs
@@ -76,12 +79,12 @@ jobs:
target: aarch64-pc-windows-msvc
steps:
- uses: actions/checkout@v5
- uses: actions/checkout@v6
- uses: dtolnay/rust-toolchain@1.90
with:
targets: ${{ matrix.target }}
- uses: actions/cache@v4
- uses: actions/cache@v5
with:
path: |
~/.cargo/bin/
@@ -98,176 +101,104 @@ jobs:
sudo apt-get install -y musl-tools pkg-config
- name: Cargo build
run: cargo build --target ${{ matrix.target }} --release --bin codex --bin codex-responses-api-proxy
- if: ${{ matrix.runner == 'macos-15-xlarge' }}
name: Configure Apple code signing
shell: bash
env:
KEYCHAIN_PASSWORD: actions
APPLE_CERTIFICATE: ${{ secrets.APPLE_CERTIFICATE_P12 }}
APPLE_CERTIFICATE_PASSWORD: ${{ secrets.APPLE_CERTIFICATE_PASSWORD }}
run: |
set -euo pipefail
if [[ -z "${APPLE_CERTIFICATE:-}" ]]; then
echo "APPLE_CERTIFICATE is required for macOS signing"
exit 1
fi
if [[ -z "${APPLE_CERTIFICATE_PASSWORD:-}" ]]; then
echo "APPLE_CERTIFICATE_PASSWORD is required for macOS signing"
exit 1
fi
cert_path="${RUNNER_TEMP}/apple_signing_certificate.p12"
echo "$APPLE_CERTIFICATE" | base64 -d > "$cert_path"
keychain_path="${RUNNER_TEMP}/codex-signing.keychain-db"
security create-keychain -p "$KEYCHAIN_PASSWORD" "$keychain_path"
security set-keychain-settings -lut 21600 "$keychain_path"
security unlock-keychain -p "$KEYCHAIN_PASSWORD" "$keychain_path"
keychain_args=()
cleanup_keychain() {
if ((${#keychain_args[@]} > 0)); then
security list-keychains -s "${keychain_args[@]}" || true
security default-keychain -s "${keychain_args[0]}" || true
else
security list-keychains -s || true
fi
if [[ -f "$keychain_path" ]]; then
security delete-keychain "$keychain_path" || true
fi
}
while IFS= read -r keychain; do
[[ -n "$keychain" ]] && keychain_args+=("$keychain")
done < <(security list-keychains | sed 's/^[[:space:]]*//;s/[[:space:]]*$//;s/"//g')
if ((${#keychain_args[@]} > 0)); then
security list-keychains -s "$keychain_path" "${keychain_args[@]}"
if [[ "${{ contains(matrix.target, 'windows') }}" == 'true' ]]; then
cargo build --target ${{ matrix.target }} --release --bin codex --bin codex-responses-api-proxy --bin codex-windows-sandbox-setup --bin codex-command-runner
else
security list-keychains -s "$keychain_path"
cargo build --target ${{ matrix.target }} --release --bin codex --bin codex-responses-api-proxy
fi
security default-keychain -s "$keychain_path"
security import "$cert_path" -k "$keychain_path" -P "$APPLE_CERTIFICATE_PASSWORD" -T /usr/bin/codesign -T /usr/bin/security
security set-key-partition-list -S apple-tool:,apple: -s -k "$KEYCHAIN_PASSWORD" "$keychain_path" > /dev/null
- if: ${{ contains(matrix.target, 'linux') }}
name: Cosign Linux artifacts
uses: ./.github/actions/linux-code-sign
with:
target: ${{ matrix.target }}
artifacts-dir: ${{ github.workspace }}/codex-rs/target/${{ matrix.target }}/release
codesign_hashes=()
while IFS= read -r hash; do
[[ -n "$hash" ]] && codesign_hashes+=("$hash")
done < <(security find-identity -v -p codesigning "$keychain_path" \
| sed -n 's/.*\([0-9A-F]\{40\}\).*/\1/p' \
| sort -u)
- if: ${{ contains(matrix.target, 'windows') }}
name: Sign Windows binaries with Azure Trusted Signing
uses: ./.github/actions/windows-code-sign
with:
target: ${{ matrix.target }}
client-id: ${{ secrets.AZURE_TRUSTED_SIGNING_CLIENT_ID }}
tenant-id: ${{ secrets.AZURE_TRUSTED_SIGNING_TENANT_ID }}
subscription-id: ${{ secrets.AZURE_TRUSTED_SIGNING_SUBSCRIPTION_ID }}
endpoint: ${{ secrets.AZURE_TRUSTED_SIGNING_ENDPOINT }}
account-name: ${{ secrets.AZURE_TRUSTED_SIGNING_ACCOUNT_NAME }}
certificate-profile-name: ${{ secrets.AZURE_TRUSTED_SIGNING_CERTIFICATE_PROFILE_NAME }}
if ((${#codesign_hashes[@]} == 0)); then
echo "No signing identities found in $keychain_path"
cleanup_keychain
rm -f "$cert_path"
exit 1
fi
- if: ${{ runner.os == 'macOS' }}
name: MacOS code signing (binaries)
uses: ./.github/actions/macos-code-sign
with:
target: ${{ matrix.target }}
sign-binaries: "true"
sign-dmg: "false"
apple-certificate: ${{ secrets.APPLE_CERTIFICATE_P12 }}
apple-certificate-password: ${{ secrets.APPLE_CERTIFICATE_PASSWORD }}
apple-notarization-key-p8: ${{ secrets.APPLE_NOTARIZATION_KEY_P8 }}
apple-notarization-key-id: ${{ secrets.APPLE_NOTARIZATION_KEY_ID }}
apple-notarization-issuer-id: ${{ secrets.APPLE_NOTARIZATION_ISSUER_ID }}
if ((${#codesign_hashes[@]} > 1)); then
echo "Multiple signing identities found in $keychain_path:"
printf ' %s\n' "${codesign_hashes[@]}"
cleanup_keychain
rm -f "$cert_path"
exit 1
fi
APPLE_CODESIGN_IDENTITY="${codesign_hashes[0]}"
rm -f "$cert_path"
echo "APPLE_CODESIGN_IDENTITY=$APPLE_CODESIGN_IDENTITY" >> "$GITHUB_ENV"
echo "APPLE_CODESIGN_KEYCHAIN=$keychain_path" >> "$GITHUB_ENV"
echo "::add-mask::$APPLE_CODESIGN_IDENTITY"
- if: ${{ matrix.runner == 'macos-15-xlarge' }}
name: Sign macOS binaries
- if: ${{ runner.os == 'macOS' }}
name: Build macOS dmg
shell: bash
run: |
set -euo pipefail
if [[ -z "${APPLE_CODESIGN_IDENTITY:-}" ]]; then
echo "APPLE_CODESIGN_IDENTITY is required for macOS signing"
target="${{ matrix.target }}"
release_dir="target/${target}/release"
dmg_root="${RUNNER_TEMP}/codex-dmg-root"
volname="Codex (${target})"
dmg_path="${release_dir}/codex-${target}.dmg"
# The previous "MacOS code signing (binaries)" step signs + notarizes the
# built artifacts in `${release_dir}`. This step packages *those same*
# signed binaries into a dmg.
codex_binary_path="${release_dir}/codex"
proxy_binary_path="${release_dir}/codex-responses-api-proxy"
rm -rf "$dmg_root"
mkdir -p "$dmg_root"
if [[ ! -f "$codex_binary_path" ]]; then
echo "Binary $codex_binary_path not found"
exit 1
fi
if [[ ! -f "$proxy_binary_path" ]]; then
echo "Binary $proxy_binary_path not found"
exit 1
fi
keychain_args=()
if [[ -n "${APPLE_CODESIGN_KEYCHAIN:-}" && -f "${APPLE_CODESIGN_KEYCHAIN}" ]]; then
keychain_args+=(--keychain "${APPLE_CODESIGN_KEYCHAIN}")
ditto "$codex_binary_path" "${dmg_root}/codex"
ditto "$proxy_binary_path" "${dmg_root}/codex-responses-api-proxy"
rm -f "$dmg_path"
hdiutil create \
-volname "$volname" \
-srcfolder "$dmg_root" \
-format UDZO \
-ov \
"$dmg_path"
if [[ ! -f "$dmg_path" ]]; then
echo "dmg $dmg_path not found after build"
exit 1
fi
for binary in codex codex-responses-api-proxy; do
path="target/${{ matrix.target }}/release/${binary}"
codesign --force --options runtime --timestamp --sign "$APPLE_CODESIGN_IDENTITY" "${keychain_args[@]}" "$path"
done
- if: ${{ matrix.runner == 'macos-15-xlarge' }}
name: Notarize macOS binaries
shell: bash
env:
APPLE_NOTARIZATION_KEY_P8: ${{ secrets.APPLE_NOTARIZATION_KEY_P8 }}
APPLE_NOTARIZATION_KEY_ID: ${{ secrets.APPLE_NOTARIZATION_KEY_ID }}
APPLE_NOTARIZATION_ISSUER_ID: ${{ secrets.APPLE_NOTARIZATION_ISSUER_ID }}
run: |
set -euo pipefail
for var in APPLE_NOTARIZATION_KEY_P8 APPLE_NOTARIZATION_KEY_ID APPLE_NOTARIZATION_ISSUER_ID; do
if [[ -z "${!var:-}" ]]; then
echo "$var is required for notarization"
exit 1
fi
done
notary_key_path="${RUNNER_TEMP}/notarytool.key.p8"
echo "$APPLE_NOTARIZATION_KEY_P8" | base64 -d > "$notary_key_path"
cleanup_notary() {
rm -f "$notary_key_path"
}
trap cleanup_notary EXIT
notarize_binary() {
local binary="$1"
local source_path="target/${{ matrix.target }}/release/${binary}"
local archive_path="${RUNNER_TEMP}/${binary}.zip"
if [[ ! -f "$source_path" ]]; then
echo "Binary $source_path not found"
exit 1
fi
rm -f "$archive_path"
ditto -c -k --keepParent "$source_path" "$archive_path"
submission_json=$(xcrun notarytool submit "$archive_path" \
--key "$notary_key_path" \
--key-id "$APPLE_NOTARIZATION_KEY_ID" \
--issuer "$APPLE_NOTARIZATION_ISSUER_ID" \
--output-format json \
--wait)
status=$(printf '%s\n' "$submission_json" | jq -r '.status // "Unknown"')
submission_id=$(printf '%s\n' "$submission_json" | jq -r '.id // ""')
if [[ -z "$submission_id" ]]; then
echo "Failed to retrieve submission ID for $binary"
exit 1
fi
echo "::notice title=Notarization::$binary submission ${submission_id} completed with status ${status}"
if [[ "$status" != "Accepted" ]]; then
echo "Notarization failed for ${binary} (submission ${submission_id}, status ${status})"
exit 1
fi
}
notarize_binary "codex"
notarize_binary "codex-responses-api-proxy"
- if: ${{ runner.os == 'macOS' }}
name: MacOS code signing (dmg)
uses: ./.github/actions/macos-code-sign
with:
target: ${{ matrix.target }}
sign-binaries: "false"
sign-dmg: "true"
apple-certificate: ${{ secrets.APPLE_CERTIFICATE_P12 }}
apple-certificate-password: ${{ secrets.APPLE_CERTIFICATE_PASSWORD }}
apple-notarization-key-p8: ${{ secrets.APPLE_NOTARIZATION_KEY_P8 }}
apple-notarization-key-id: ${{ secrets.APPLE_NOTARIZATION_KEY_ID }}
apple-notarization-issuer-id: ${{ secrets.APPLE_NOTARIZATION_ISSUER_ID }}
- name: Stage artifacts
shell: bash
@@ -278,11 +209,22 @@ jobs:
if [[ "${{ matrix.runner }}" == windows* ]]; then
cp target/${{ matrix.target }}/release/codex.exe "$dest/codex-${{ matrix.target }}.exe"
cp target/${{ matrix.target }}/release/codex-responses-api-proxy.exe "$dest/codex-responses-api-proxy-${{ matrix.target }}.exe"
cp target/${{ matrix.target }}/release/codex-windows-sandbox-setup.exe "$dest/codex-windows-sandbox-setup-${{ matrix.target }}.exe"
cp target/${{ matrix.target }}/release/codex-command-runner.exe "$dest/codex-command-runner-${{ matrix.target }}.exe"
else
cp target/${{ matrix.target }}/release/codex "$dest/codex-${{ matrix.target }}"
cp target/${{ matrix.target }}/release/codex-responses-api-proxy "$dest/codex-responses-api-proxy-${{ matrix.target }}"
fi
if [[ "${{ matrix.target }}" == *linux* ]]; then
cp target/${{ matrix.target }}/release/codex.sigstore "$dest/codex-${{ matrix.target }}.sigstore"
cp target/${{ matrix.target }}/release/codex-responses-api-proxy.sigstore "$dest/codex-responses-api-proxy-${{ matrix.target }}.sigstore"
fi
if [[ "${{ matrix.target }}" == *apple-darwin ]]; then
cp target/${{ matrix.target }}/release/codex-${{ matrix.target }}.dmg "$dest/codex-${{ matrix.target }}.dmg"
fi
- if: ${{ matrix.runner == 'windows-11-arm' }}
name: Install zstd
shell: powershell
@@ -295,6 +237,15 @@ jobs:
# ${{ matrix.target }}
dest="dist/${{ matrix.target }}"
# We want to ship the raw Windows executables in the GitHub Release
# in addition to the compressed archives. Keep the originals for
# Windows targets; remove them elsewhere to limit the number of
# artifacts that end up in the GitHub Release.
keep_originals=false
if [[ "${{ matrix.runner }}" == windows* ]]; then
keep_originals=true
fi
# For compatibility with environments that lack the `zstd` tool we
# additionally create a `.tar.gz` for all platforms and `.zip` for
# Windows alongside every single binary that we publish. The end result is:
@@ -308,7 +259,12 @@ jobs:
base="$(basename "$f")"
# Skip files that are already archives (shouldn't happen, but be
# safe).
if [[ "$base" == *.tar.gz || "$base" == *.zip ]]; then
if [[ "$base" == *.tar.gz || "$base" == *.zip || "$base" == *.dmg ]]; then
continue
fi
# Don't try to compress signature bundles.
if [[ "$base" == *.sigstore ]]; then
continue
fi
@@ -324,33 +280,14 @@ jobs:
# Also create .zst (existing behaviour) *and* remove the original
# uncompressed binary to keep the directory small.
zstd -T0 -19 --rm "$dest/$base"
zstd_args=(-T0 -19)
if [[ "${keep_originals}" == false ]]; then
zstd_args+=(--rm)
fi
zstd "${zstd_args[@]}" "$dest/$base"
done
- name: Remove signing keychain
if: ${{ always() && matrix.runner == 'macos-15-xlarge' }}
shell: bash
env:
APPLE_CODESIGN_KEYCHAIN: ${{ env.APPLE_CODESIGN_KEYCHAIN }}
run: |
set -euo pipefail
if [[ -n "${APPLE_CODESIGN_KEYCHAIN:-}" ]]; then
keychain_args=()
while IFS= read -r keychain; do
[[ "$keychain" == "$APPLE_CODESIGN_KEYCHAIN" ]] && continue
[[ -n "$keychain" ]] && keychain_args+=("$keychain")
done < <(security list-keychains | sed 's/^[[:space:]]*//;s/[[:space:]]*$//;s/"//g')
if ((${#keychain_args[@]} > 0)); then
security list-keychains -s "${keychain_args[@]}"
security default-keychain -s "${keychain_args[0]}"
fi
if [[ -f "$APPLE_CODESIGN_KEYCHAIN" ]]; then
security delete-keychain "$APPLE_CODESIGN_KEYCHAIN"
fi
fi
- uses: actions/upload-artifact@v4
- uses: actions/upload-artifact@v6
with:
name: ${{ matrix.target }}
# Upload the per-binary .zst files as well as the new .tar.gz
@@ -358,8 +295,19 @@ jobs:
path: |
codex-rs/dist/${{ matrix.target }}/*
shell-tool-mcp:
name: shell-tool-mcp
needs: tag-check
uses: ./.github/workflows/shell-tool-mcp.yml
with:
release-tag: ${{ github.ref_name }}
publish: true
secrets: inherit
release:
needs: build
needs:
- build
- shell-tool-mcp
name: release
runs-on: ubuntu-latest
permissions:
@@ -373,15 +321,43 @@ jobs:
steps:
- name: Checkout repository
uses: actions/checkout@v5
uses: actions/checkout@v6
- uses: actions/download-artifact@v4
- name: Generate release notes from tag commit message
id: release_notes
shell: bash
run: |
set -euo pipefail
# On tag pushes, GITHUB_SHA may be a tag object for annotated tags;
# peel it to the underlying commit.
commit="$(git rev-parse "${GITHUB_SHA}^{commit}")"
notes_path="${RUNNER_TEMP}/release-notes.md"
# Use the commit message for the commit the tag points at (not the
# annotated tag message).
git log -1 --format=%B "${commit}" > "${notes_path}"
# Ensure trailing newline so GitHub's markdown renderer doesn't
# occasionally run the last line into subsequent content.
echo >> "${notes_path}"
echo "path=${notes_path}" >> "${GITHUB_OUTPUT}"
- uses: actions/download-artifact@v7
with:
path: dist
- name: List
run: ls -R dist/
# This is a temporary fix: we should modify shell-tool-mcp.yml so these
# files do not end up in dist/ in the first place.
- name: Delete entries from dist/ that should not go in the release
run: |
rm -rf dist/shell-tool-mcp*
ls -R dist/
- name: Define release name
id: release_name
run: |
@@ -415,7 +391,7 @@ jobs:
run_install: false
- name: Setup Node.js for npm packaging
uses: actions/setup-node@v5
uses: actions/setup-node@v6
with:
node-version: 22
@@ -439,6 +415,7 @@ jobs:
with:
name: ${{ steps.release_name.outputs.name }}
tag_name: ${{ github.ref_name }}
body_path: ${{ steps.release_notes.outputs.path }}
files: dist/**
# Mark as prerelease only when the version has a suffix after x.y.z
# (e.g. -alpha, -beta). Otherwise publish a normal release.
@@ -466,7 +443,7 @@ jobs:
steps:
- name: Setup Node.js
uses: actions/setup-node@v5
uses: actions/setup-node@v6
with:
node-version: 22
registry-url: "https://registry.npmjs.org"

View File

@@ -11,7 +11,7 @@ jobs:
timeout-minutes: 10
steps:
- name: Checkout repository
uses: actions/checkout@v5
uses: actions/checkout@v6
- name: Setup pnpm
uses: pnpm/action-setup@v4
@@ -19,7 +19,7 @@ jobs:
run_install: false
- name: Setup Node.js
uses: actions/setup-node@v5
uses: actions/setup-node@v6
with:
node-version: 22
cache: pnpm

48
.github/workflows/shell-tool-mcp-ci.yml vendored Normal file
View File

@@ -0,0 +1,48 @@
name: shell-tool-mcp CI
on:
push:
paths:
- "shell-tool-mcp/**"
- ".github/workflows/shell-tool-mcp-ci.yml"
- "pnpm-lock.yaml"
- "pnpm-workspace.yaml"
pull_request:
paths:
- "shell-tool-mcp/**"
- ".github/workflows/shell-tool-mcp-ci.yml"
- "pnpm-lock.yaml"
- "pnpm-workspace.yaml"
env:
NODE_VERSION: 22
jobs:
test:
runs-on: ubuntu-latest
steps:
- name: Checkout repository
uses: actions/checkout@v6
- name: Setup pnpm
uses: pnpm/action-setup@v4
with:
run_install: false
- name: Setup Node.js
uses: actions/setup-node@v6
with:
node-version: ${{ env.NODE_VERSION }}
cache: "pnpm"
- name: Install dependencies
run: pnpm install --frozen-lockfile
- name: Format check
run: pnpm --filter @openai/codex-shell-tool-mcp run format
- name: Run tests
run: pnpm --filter @openai/codex-shell-tool-mcp test
- name: Build
run: pnpm --filter @openai/codex-shell-tool-mcp run build

405
.github/workflows/shell-tool-mcp.yml vendored Normal file
View File

@@ -0,0 +1,405 @@
name: shell-tool-mcp
on:
workflow_call:
inputs:
release-version:
description: Version to publish (x.y.z or x.y.z-alpha.N). Defaults to GITHUB_REF_NAME when it starts with rust-v.
required: false
type: string
release-tag:
description: Tag name to use when downloading release artifacts (defaults to rust-v<version>).
required: false
type: string
publish:
description: Whether to publish to npm when the version is releasable.
required: false
default: true
type: boolean
env:
NODE_VERSION: 22
jobs:
metadata:
runs-on: ubuntu-latest
outputs:
version: ${{ steps.compute.outputs.version }}
release_tag: ${{ steps.compute.outputs.release_tag }}
should_publish: ${{ steps.compute.outputs.should_publish }}
npm_tag: ${{ steps.compute.outputs.npm_tag }}
steps:
- name: Compute version and tags
id: compute
run: |
set -euo pipefail
version="${{ inputs.release-version }}"
release_tag="${{ inputs.release-tag }}"
if [[ -z "$version" ]]; then
if [[ -n "$release_tag" && "$release_tag" =~ ^rust-v.+ ]]; then
version="${release_tag#rust-v}"
elif [[ "${GITHUB_REF_NAME:-}" =~ ^rust-v.+ ]]; then
version="${GITHUB_REF_NAME#rust-v}"
release_tag="${GITHUB_REF_NAME}"
else
echo "release-version is required when GITHUB_REF_NAME is not a rust-v tag."
exit 1
fi
fi
if [[ -z "$release_tag" ]]; then
release_tag="rust-v${version}"
fi
npm_tag=""
should_publish="false"
if [[ "$version" =~ ^[0-9]+\.[0-9]+\.[0-9]+$ ]]; then
should_publish="true"
elif [[ "$version" =~ ^[0-9]+\.[0-9]+\.[0-9]+-alpha\.[0-9]+$ ]]; then
should_publish="true"
npm_tag="alpha"
fi
echo "version=${version}" >> "$GITHUB_OUTPUT"
echo "release_tag=${release_tag}" >> "$GITHUB_OUTPUT"
echo "npm_tag=${npm_tag}" >> "$GITHUB_OUTPUT"
echo "should_publish=${should_publish}" >> "$GITHUB_OUTPUT"
rust-binaries:
name: Build Rust - ${{ matrix.target }}
needs: metadata
runs-on: ${{ matrix.runner }}
timeout-minutes: 30
defaults:
run:
working-directory: codex-rs
strategy:
fail-fast: false
matrix:
include:
- runner: macos-15-xlarge
target: aarch64-apple-darwin
- runner: macos-15-xlarge
target: x86_64-apple-darwin
- runner: ubuntu-24.04
target: x86_64-unknown-linux-musl
install_musl: true
- runner: ubuntu-24.04-arm
target: aarch64-unknown-linux-musl
install_musl: true
steps:
- name: Checkout repository
uses: actions/checkout@v6
- uses: dtolnay/rust-toolchain@1.90
with:
targets: ${{ matrix.target }}
- if: ${{ matrix.install_musl }}
name: Install musl build dependencies
run: |
sudo apt-get update
sudo apt-get install -y musl-tools pkg-config
- name: Build exec server binaries
run: cargo build --release --target ${{ matrix.target }} --bin codex-exec-mcp-server --bin codex-execve-wrapper
- name: Stage exec server binaries
run: |
dest="${GITHUB_WORKSPACE}/artifacts/vendor/${{ matrix.target }}"
mkdir -p "$dest"
cp "target/${{ matrix.target }}/release/codex-exec-mcp-server" "$dest/"
cp "target/${{ matrix.target }}/release/codex-execve-wrapper" "$dest/"
- uses: actions/upload-artifact@v6
with:
name: shell-tool-mcp-rust-${{ matrix.target }}
path: artifacts/**
if-no-files-found: error
bash-linux:
name: Build Bash (Linux) - ${{ matrix.variant }} - ${{ matrix.target }}
needs: metadata
runs-on: ${{ matrix.runner }}
timeout-minutes: 30
container:
image: ${{ matrix.image }}
strategy:
fail-fast: false
matrix:
include:
- runner: ubuntu-24.04
target: x86_64-unknown-linux-musl
variant: ubuntu-24.04
image: ubuntu:24.04
- runner: ubuntu-24.04
target: x86_64-unknown-linux-musl
variant: ubuntu-22.04
image: ubuntu:22.04
- runner: ubuntu-24.04
target: x86_64-unknown-linux-musl
variant: debian-12
image: debian:12
- runner: ubuntu-24.04
target: x86_64-unknown-linux-musl
variant: debian-11
image: debian:11
- runner: ubuntu-24.04
target: x86_64-unknown-linux-musl
variant: centos-9
image: quay.io/centos/centos:stream9
- runner: ubuntu-24.04-arm
target: aarch64-unknown-linux-musl
variant: ubuntu-24.04
image: arm64v8/ubuntu:24.04
- runner: ubuntu-24.04-arm
target: aarch64-unknown-linux-musl
variant: ubuntu-22.04
image: arm64v8/ubuntu:22.04
- runner: ubuntu-24.04-arm
target: aarch64-unknown-linux-musl
variant: ubuntu-20.04
image: arm64v8/ubuntu:20.04
- runner: ubuntu-24.04-arm
target: aarch64-unknown-linux-musl
variant: debian-12
image: arm64v8/debian:12
- runner: ubuntu-24.04-arm
target: aarch64-unknown-linux-musl
variant: debian-11
image: arm64v8/debian:11
- runner: ubuntu-24.04-arm
target: aarch64-unknown-linux-musl
variant: centos-9
image: quay.io/centos/centos:stream9
steps:
- name: Install build prerequisites
shell: bash
run: |
set -euo pipefail
if command -v apt-get >/dev/null 2>&1; then
apt-get update
DEBIAN_FRONTEND=noninteractive apt-get install -y git build-essential bison autoconf gettext
elif command -v dnf >/dev/null 2>&1; then
dnf install -y git gcc gcc-c++ make bison autoconf gettext
elif command -v yum >/dev/null 2>&1; then
yum install -y git gcc gcc-c++ make bison autoconf gettext
else
echo "Unsupported package manager in container"
exit 1
fi
- name: Checkout repository
uses: actions/checkout@v6
- name: Build patched Bash
shell: bash
run: |
set -euo pipefail
git clone --depth 1 https://github.com/bminor/bash /tmp/bash
cd /tmp/bash
git fetch --depth 1 origin a8a1c2fac029404d3f42cd39f5a20f24b6e4fe4b
git checkout a8a1c2fac029404d3f42cd39f5a20f24b6e4fe4b
git apply "${GITHUB_WORKSPACE}/shell-tool-mcp/patches/bash-exec-wrapper.patch"
./configure --without-bash-malloc
cores="$(command -v nproc >/dev/null 2>&1 && nproc || getconf _NPROCESSORS_ONLN)"
make -j"${cores}"
dest="${GITHUB_WORKSPACE}/artifacts/vendor/${{ matrix.target }}/bash/${{ matrix.variant }}"
mkdir -p "$dest"
cp bash "$dest/bash"
- uses: actions/upload-artifact@v6
with:
name: shell-tool-mcp-bash-${{ matrix.target }}-${{ matrix.variant }}
path: artifacts/**
if-no-files-found: error
bash-darwin:
name: Build Bash (macOS) - ${{ matrix.variant }} - ${{ matrix.target }}
needs: metadata
runs-on: ${{ matrix.runner }}
timeout-minutes: 30
strategy:
fail-fast: false
matrix:
include:
- runner: macos-15-xlarge
target: aarch64-apple-darwin
variant: macos-15
- runner: macos-14
target: aarch64-apple-darwin
variant: macos-14
steps:
- name: Checkout repository
uses: actions/checkout@v6
- name: Build patched Bash
shell: bash
run: |
set -euo pipefail
git clone --depth 1 https://github.com/bminor/bash /tmp/bash
cd /tmp/bash
git fetch --depth 1 origin a8a1c2fac029404d3f42cd39f5a20f24b6e4fe4b
git checkout a8a1c2fac029404d3f42cd39f5a20f24b6e4fe4b
git apply "${GITHUB_WORKSPACE}/shell-tool-mcp/patches/bash-exec-wrapper.patch"
./configure --without-bash-malloc
cores="$(getconf _NPROCESSORS_ONLN)"
make -j"${cores}"
dest="${GITHUB_WORKSPACE}/artifacts/vendor/${{ matrix.target }}/bash/${{ matrix.variant }}"
mkdir -p "$dest"
cp bash "$dest/bash"
- uses: actions/upload-artifact@v6
with:
name: shell-tool-mcp-bash-${{ matrix.target }}-${{ matrix.variant }}
path: artifacts/**
if-no-files-found: error
package:
name: Package npm module
needs:
- metadata
- rust-binaries
- bash-linux
- bash-darwin
runs-on: ubuntu-latest
env:
PACKAGE_VERSION: ${{ needs.metadata.outputs.version }}
steps:
- name: Checkout repository
uses: actions/checkout@v6
- name: Setup pnpm
uses: pnpm/action-setup@v4
with:
version: 10.8.1
run_install: false
- name: Setup Node.js
uses: actions/setup-node@v6
with:
node-version: ${{ env.NODE_VERSION }}
- name: Install JavaScript dependencies
run: pnpm install --frozen-lockfile
- name: Build (shell-tool-mcp)
run: pnpm --filter @openai/codex-shell-tool-mcp run build
- name: Download build artifacts
uses: actions/download-artifact@v7
with:
path: artifacts
- name: Assemble staging directory
id: staging
shell: bash
run: |
set -euo pipefail
staging="${STAGING_DIR}"
mkdir -p "$staging" "$staging/vendor"
cp shell-tool-mcp/README.md "$staging/"
cp shell-tool-mcp/package.json "$staging/"
cp -R shell-tool-mcp/bin "$staging/"
found_vendor="false"
shopt -s nullglob
for vendor_dir in artifacts/*/vendor; do
rsync -av "$vendor_dir/" "$staging/vendor/"
found_vendor="true"
done
if [[ "$found_vendor" == "false" ]]; then
echo "No vendor payloads were downloaded."
exit 1
fi
node - <<'NODE'
import fs from "node:fs";
import path from "node:path";
const stagingDir = process.env.STAGING_DIR;
const version = process.env.PACKAGE_VERSION;
const pkgPath = path.join(stagingDir, "package.json");
const pkg = JSON.parse(fs.readFileSync(pkgPath, "utf8"));
pkg.version = version;
fs.writeFileSync(pkgPath, JSON.stringify(pkg, null, 2) + "\n");
NODE
echo "dir=$staging" >> "$GITHUB_OUTPUT"
env:
STAGING_DIR: ${{ runner.temp }}/shell-tool-mcp
- name: Ensure binaries are executable
run: |
set -euo pipefail
staging="${{ steps.staging.outputs.dir }}"
chmod +x \
"$staging"/vendor/*/codex-exec-mcp-server \
"$staging"/vendor/*/codex-execve-wrapper \
"$staging"/vendor/*/bash/*/bash
- name: Create npm tarball
shell: bash
run: |
set -euo pipefail
mkdir -p dist/npm
staging="${{ steps.staging.outputs.dir }}"
pack_info=$(cd "$staging" && npm pack --ignore-scripts --json --pack-destination "${GITHUB_WORKSPACE}/dist/npm")
filename=$(PACK_INFO="$pack_info" node -e 'const data = JSON.parse(process.env.PACK_INFO); console.log(data[0].filename);')
mv "dist/npm/${filename}" "dist/npm/codex-shell-tool-mcp-npm-${PACKAGE_VERSION}.tgz"
- uses: actions/upload-artifact@v6
with:
name: codex-shell-tool-mcp-npm
path: dist/npm/codex-shell-tool-mcp-npm-${{ env.PACKAGE_VERSION }}.tgz
if-no-files-found: error
publish:
name: Publish npm package
needs:
- metadata
- package
if: ${{ inputs.publish && needs.metadata.outputs.should_publish == 'true' }}
runs-on: ubuntu-latest
permissions:
id-token: write
contents: read
steps:
- name: Setup pnpm
uses: pnpm/action-setup@v4
with:
version: 10.8.1
run_install: false
- name: Setup Node.js
uses: actions/setup-node@v6
with:
node-version: ${{ env.NODE_VERSION }}
registry-url: https://registry.npmjs.org
scope: "@openai"
- name: Update npm
run: npm install -g npm@latest
- name: Download npm tarball
uses: actions/download-artifact@v7
with:
name: codex-shell-tool-mcp-npm
path: dist/npm
- name: Publish to npm
env:
NPM_TAG: ${{ needs.metadata.outputs.npm_tag }}
VERSION: ${{ needs.metadata.outputs.version }}
shell: bash
run: |
set -euo pipefail
tag_args=()
if [[ -n "${NPM_TAG}" ]]; then
tag_args+=(--tag "${NPM_TAG}")
fi
npm publish "dist/npm/codex-shell-tool-mcp-npm-${VERSION}.tgz" "${tag_args[@]}"

9
.gitignore vendored
View File

@@ -9,6 +9,7 @@ node_modules
# build
dist/
bazel-*
build/
out/
storybook-static/
@@ -64,6 +65,9 @@ apply_patch/
# coverage
coverage/
# personal files
personal/
# os
.DS_Store
Thumbs.db
@@ -82,3 +86,8 @@ CHANGELOG.ignore.md
# nix related
.direnv
.envrc
# Python bytecode files
__pycache__/
*.pyc

View File

@@ -11,7 +11,6 @@ In the codex-rs folder where the rust code lives:
- Always collapse if statements per https://rust-lang.github.io/rust-clippy/master/index.html#collapsible_if
- Always inline format! args when possible per https://rust-lang.github.io/rust-clippy/master/index.html#uninlined_format_args
- Use method references over closures when possible per https://rust-lang.github.io/rust-clippy/master/index.html#redundant_closure_for_method_calls
- Do not use unsigned integer even if the number cannot be negative.
- When writing tests, prefer comparing the equality of entire objects over fields one by one.
- When making a change that adds or changes an API, ensure that the documentation in the `docs/` folder is up to date if applicable.
@@ -75,6 +74,14 @@ If you dont have the tool:
### Test assertions
- Tests should use pretty_assertions::assert_eq for clearer diffs. Import this at the top of the test module if it isn't already.
- Prefer deep equals comparisons whenever possible. Perform `assert_eq!()` on entire objects, rather than individual fields.
- Avoid mutating process environment in tests; prefer passing environment-derived flags or dependencies from above.
### Spawning workspace binaries in tests (Cargo vs Bazel)
- Prefer `codex_utils_cargo_bin::cargo_bin("...")` over `assert_cmd::Command::cargo_bin(...)` or `escargot` when tests need to spawn first-party binaries.
- Under Bazel, binaries and resources may live under runfiles; use `codex_utils_cargo_bin::cargo_bin` to resolve absolute paths that remain stable after `chdir`.
- When locating fixture files or test resources under Bazel, avoid `env!("CARGO_MANIFEST_DIR")`. Prefer `codex_utils_cargo_bin::find_resource!` so paths resolve correctly under both Cargo and Bazel runfiles.
### Integration tests (core)
@@ -84,6 +91,8 @@ If you dont have the tool:
- Use `ResponseMock::single_request()` when a test should only issue one POST, or `ResponseMock::requests()` to inspect every captured `ResponsesRequest`.
- `ResponsesRequest` exposes helpers (`body_json`, `input`, `function_call_output`, `custom_tool_call_output`, `call_output`, `header`, `path`, `query_param`) so assertions can target structured payloads instead of manual JSON digging.
- Build SSE payloads with the provided `ev_*` constructors and the `sse(...)`.
- Prefer `wait_for_event` over `wait_for_event_with_timeout`.
- Prefer `mount_sse_once` over `mount_sse_once_match` or `mount_sse_sequence`
- Typical pattern:

19
BUILD.bazel Normal file
View File

@@ -0,0 +1,19 @@
# We mark the local platform as glibc-compatible so that rust can grab a toolchain for us.
# TODO(zbarsky): Upstream a better libc constraint into rules_rust.
# We only enable this on linux though for sanity, and because it breaks remote execution.
platform(
name = "local",
constraint_values = [
"@toolchains_llvm_bootstrapped//constraints/libc:gnu.2.28",
],
parents = [
"@platforms//host",
],
)
alias(
name = "rbe",
actual = "@rbe_platform",
)
exports_files(["AGENTS.md"])

View File

@@ -1 +1 @@
The changelog can be found on the [releases page](https://github.com/openai/codex/releases)
The changelog can be found on the [releases page](https://github.com/openai/codex/releases).

128
MODULE.bazel Normal file
View File

@@ -0,0 +1,128 @@
bazel_dep(name = "platforms", version = "1.0.0")
bazel_dep(name = "toolchains_llvm_bootstrapped", version = "0.3.1")
archive_override(
module_name = "toolchains_llvm_bootstrapped",
integrity = "sha256-9ks21bgEqbQWmwUIvqeLA64+Jk6o4ZVjC8KxjVa2Vw8=",
strip_prefix = "toolchains_llvm_bootstrapped-e3775e66a7b6d287c705ca0cd24497ef4a77c503",
urls = ["https://github.com/cerisier/toolchains_llvm_bootstrapped/archive/e3775e66a7b6d287c705ca0cd24497ef4a77c503/master.tar.gz"],
patch_strip = 1,
patches = [
"//patches:llvm_toolchain_archive_params.patch",
],
)
osx = use_extension("@toolchains_llvm_bootstrapped//toolchain/extension:osx.bzl", "osx")
osx.framework(name = "ApplicationServices")
osx.framework(name = "AppKit")
osx.framework(name = "ColorSync")
osx.framework(name = "CoreFoundation")
osx.framework(name = "CoreGraphics")
osx.framework(name = "CoreServices")
osx.framework(name = "CoreText")
osx.framework(name = "CFNetwork")
osx.framework(name = "Foundation")
osx.framework(name = "ImageIO")
osx.framework(name = "Kernel")
osx.framework(name = "OSLog")
osx.framework(name = "Security")
osx.framework(name = "SystemConfiguration")
register_toolchains(
"@toolchains_llvm_bootstrapped//toolchain:all",
)
bazel_dep(name = "rules_cc", version = "0.2.16")
bazel_dep(name = "rules_platform", version = "0.1.0")
bazel_dep(name = "rules_rust", version = "0.68.1")
single_version_override(
module_name = "rules_rust",
patch_strip = 1,
patches = [
"//patches:rules_rust.patch",
"//patches:rules_rust_windows_gnu.patch",
"//patches:rules_rust_musl.patch",
],
)
RUST_TRIPLES = [
"aarch64-unknown-linux-musl",
"aarch64-apple-darwin",
"aarch64-pc-windows-gnullvm",
"x86_64-unknown-linux-musl",
"x86_64-apple-darwin",
"x86_64-pc-windows-gnullvm",
]
rust = use_extension("@rules_rust//rust:extensions.bzl", "rust")
rust.toolchain(
edition = "2024",
extra_target_triples = RUST_TRIPLES,
versions = ["1.90.0"],
)
use_repo(rust, "rust_toolchains")
register_toolchains("@rust_toolchains//:all")
bazel_dep(name = "rules_rs", version = "0.0.23")
crate = use_extension("@rules_rs//rs:extensions.bzl", "crate")
crate.from_cargo(
cargo_lock = "//codex-rs:Cargo.lock",
cargo_toml = "//codex-rs:Cargo.toml",
platform_triples = RUST_TRIPLES,
)
bazel_dep(name = "openssl", version = "3.5.4.bcr.0")
crate.annotation(
build_script_data = [
"@openssl//:gen_dir",
],
build_script_env = {
"OPENSSL_DIR": "$(execpath @openssl//:gen_dir)",
"OPENSSL_NO_VENDOR": "1",
"OPENSSL_STATIC": "1",
},
crate = "openssl-sys",
data = ["@openssl//:gen_dir"],
)
inject_repo(crate, "openssl")
# Fix readme inclusions
crate.annotation(
crate = "windows-link",
patch_args = ["-p1"],
patches = [
"//patches:windows-link.patch"
],
)
WINDOWS_IMPORT_LIB = """
load("@rules_cc//cc:defs.bzl", "cc_import")
cc_import(
name = "windows_import_lib",
static_library = glob(["lib/*.a"])[0],
)
"""
crate.annotation(
additive_build_file_content = WINDOWS_IMPORT_LIB,
crate = "windows_x86_64_gnullvm",
gen_build_script = "off",
deps = [":windows_import_lib"],
)
crate.annotation(
additive_build_file_content = WINDOWS_IMPORT_LIB,
crate = "windows_aarch64_gnullvm",
gen_build_script = "off",
deps = [":windows_import_lib"],
)
use_repo(crate, "crates")
rbe_platform_repository = use_repo_rule("//:rbe.bzl", "rbe_platform_repository")
rbe_platform_repository(
name = "rbe_platform",
)

1097
MODULE.bazel.lock generated Normal file

File diff suppressed because one or more lines are too long

View File

@@ -1,13 +1,11 @@
<p align="center"><code>npm i -g @openai/codex</code><br />or <code>brew install --cask codex</code></p>
<p align="center"><strong>Codex CLI</strong> is a coding agent from OpenAI that runs locally on your computer.
</br>
</br>If you want Codex in your code editor (VS Code, Cursor, Windsurf), <a href="https://developers.openai.com/codex/ide">install in your IDE</a>
</br>If you are looking for the <em>cloud-based agent</em> from OpenAI, <strong>Codex Web</strong>, go to <a href="https://chatgpt.com/codex">chatgpt.com/codex</a></p>
<p align="center">
<img src="./.github/codex-cli-splash.png" alt="Codex CLI splash" width="80%" />
</p>
</p>
</br>
If you want Codex in your code editor (VS Code, Cursor, Windsurf), <a href="https://developers.openai.com/codex/ide">install in your IDE.</a>
</br>If you are looking for the <em>cloud-based agent</em> from OpenAI, <strong>Codex Web</strong>, go to <a href="https://chatgpt.com/codex">chatgpt.com/codex</a>.</p>
---
@@ -15,23 +13,19 @@
### Installing and running Codex CLI
Install globally with your preferred package manager. If you use npm:
Install globally with your preferred package manager:
```shell
# Install using npm
npm install -g @openai/codex
```
Alternatively, if you use Homebrew:
```shell
# Install using Homebrew
brew install --cask codex
```
Then simply run `codex` to get started:
```shell
codex
```
Then simply run `codex` to get started.
<details>
<summary>You can also go to the <a href="https://github.com/openai/codex/releases/latest">latest GitHub Release</a> and download the appropriate binary for your platform.</summary>
@@ -51,55 +45,15 @@ Each archive contains a single entry with the platform baked into the name (e.g.
### Using Codex with your ChatGPT plan
<p align="center">
<img src="./.github/codex-cli-login.png" alt="Codex CLI login" width="80%" />
</p>
Run `codex` and select **Sign in with ChatGPT**. We recommend signing into your ChatGPT account to use Codex as part of your Plus, Pro, Team, Edu, or Enterprise plan. [Learn more about what's included in your ChatGPT plan](https://help.openai.com/en/articles/11369540-codex-in-chatgpt).
You can also use Codex with an API key, but this requires [additional setup](./docs/authentication.md#usage-based-billing-alternative-use-an-openai-api-key). If you previously used an API key for usage-based billing, see the [migration steps](./docs/authentication.md#migrating-from-usage-based-billing-api-key). If you're having trouble with login, please comment on [this issue](https://github.com/openai/codex/issues/1243).
You can also use Codex with an API key, but this requires [additional setup](https://developers.openai.com/codex/auth#sign-in-with-an-api-key).
### Model Context Protocol (MCP)
## Docs
Codex can access MCP servers. To configure them, refer to the [config docs](./docs/config.md#mcp_servers).
### Configuration
Codex CLI supports a rich set of configuration options, with preferences stored in `~/.codex/config.toml`. For full configuration options, see [Configuration](./docs/config.md).
---
### Docs & FAQ
- [**Getting started**](./docs/getting-started.md)
- [CLI usage](./docs/getting-started.md#cli-usage)
- [Running with a prompt as input](./docs/getting-started.md#running-with-a-prompt-as-input)
- [Example prompts](./docs/getting-started.md#example-prompts)
- [Custom prompts](./docs/prompts.md)
- [Memory with AGENTS.md](./docs/getting-started.md#memory-with-agentsmd)
- [Configuration](./docs/config.md)
- [**Sandbox & approvals**](./docs/sandbox.md)
- [**Authentication**](./docs/authentication.md)
- [Auth methods](./docs/authentication.md#forcing-a-specific-auth-method-advanced)
- [Login on a "Headless" machine](./docs/authentication.md#connecting-on-a-headless-machine)
- **Automating Codex**
- [GitHub Action](https://github.com/openai/codex-action)
- [TypeScript SDK](./sdk/typescript/README.md)
- [Non-interactive mode (`codex exec`)](./docs/exec.md)
- [**Advanced**](./docs/advanced.md)
- [Tracing / verbose logging](./docs/advanced.md#tracing--verbose-logging)
- [Model Context Protocol (MCP)](./docs/advanced.md#model-context-protocol-mcp)
- [**Zero data retention (ZDR)**](./docs/zdr.md)
- [**Codex Documentation**](https://developers.openai.com/codex)
- [**Contributing**](./docs/contributing.md)
- [**Install & build**](./docs/install.md)
- [System Requirements](./docs/install.md#system-requirements)
- [DotSlash](./docs/install.md#dotslash)
- [Build from source](./docs/install.md#build-from-source)
- [**FAQ**](./docs/faq.md)
- [**Installing & building**](./docs/install.md)
- [**Open source fund**](./docs/open-source-fund.md)
---
## License
This repository is licensed under the [Apache-2.0 License](LICENSE).

17
announcement_tip.toml Normal file
View File

@@ -0,0 +1,17 @@
# Example announcement tips for Codex TUI.
# Each [[announcements]] entry is evaluated in order; the last matching one is shown.
# Dates are UTC, formatted as YYYY-MM-DD. The from_date is inclusive and the to_date is exclusive.
# version_regex matches against the CLI version (env!("CARGO_PKG_VERSION")); omit to apply to all versions.
# target_app specify which app should display the announcement (cli, vsce, ...).
[[announcements]]
content = "Welcome to Codex! Check out the new onboarding flow."
from_date = "2024-10-01"
to_date = "2024-10-15"
target_app = "cli"
# Test announcement only for local build version until 2026-01-10 excluded (past)
[[announcements]]
content = "This is a test announcement"
version_regex = "^0\\.0\\.0$"
to_date = "2026-01-10"

View File

@@ -96,9 +96,8 @@ function detectPackageManager() {
}
if (
process.env.BUN_INSTALL ||
process.env.BUN_INSTALL_GLOBAL_DIR ||
process.env.BUN_INSTALL_BIN_DIR
__dirname.includes(".bun/install/global") ||
__dirname.includes(".bun\\install\\global")
) {
return "bun";
}

View File

@@ -20,9 +20,14 @@ PACKAGE_NATIVE_COMPONENTS: dict[str, list[str]] = {
"codex-responses-api-proxy": ["codex-responses-api-proxy"],
"codex-sdk": ["codex"],
}
WINDOWS_ONLY_COMPONENTS: dict[str, list[str]] = {
"codex": ["codex-windows-sandbox-setup", "codex-command-runner"],
}
COMPONENT_DEST_DIR: dict[str, str] = {
"codex": "codex",
"codex-responses-api-proxy": "codex-responses-api-proxy",
"codex-windows-sandbox-setup": "codex",
"codex-command-runner": "codex",
"rg": "path",
}
@@ -103,7 +108,7 @@ def main() -> int:
"pointing to a directory containing pre-installed binaries."
)
copy_native_binaries(vendor_src, staging_dir, native_components)
copy_native_binaries(vendor_src, staging_dir, package, native_components)
if release_version:
staging_dir_str = str(staging_dir)
@@ -232,7 +237,12 @@ def stage_codex_sdk_sources(staging_dir: Path) -> None:
shutil.copy2(license_src, staging_dir / "LICENSE")
def copy_native_binaries(vendor_src: Path, staging_dir: Path, components: list[str]) -> None:
def copy_native_binaries(
vendor_src: Path,
staging_dir: Path,
package: str,
components: list[str],
) -> None:
vendor_src = vendor_src.resolve()
if not vendor_src.exists():
raise RuntimeError(f"Vendor source directory not found: {vendor_src}")
@@ -250,6 +260,9 @@ def copy_native_binaries(vendor_src: Path, staging_dir: Path, components: list[s
if not target_dir.is_dir():
continue
if "windows" in target_dir.name:
components_set.update(WINDOWS_ONLY_COMPONENTS.get(package, []))
dest_target_dir = vendor_dest / target_dir.name
dest_target_dir.mkdir(parents=True, exist_ok=True)

View File

@@ -2,6 +2,7 @@
"""Install Codex native binaries (Rust CLI plus ripgrep helpers)."""
import argparse
from contextlib import contextmanager
import json
import os
import shutil
@@ -12,6 +13,7 @@ import zipfile
from dataclasses import dataclass
from concurrent.futures import ThreadPoolExecutor, as_completed
from pathlib import Path
import sys
from typing import Iterable, Sequence
from urllib.parse import urlparse
from urllib.request import urlopen
@@ -36,8 +38,11 @@ class BinaryComponent:
artifact_prefix: str # matches the artifact filename prefix (e.g. codex-<target>.zst)
dest_dir: str # directory under vendor/<target>/ where the binary is installed
binary_basename: str # executable name inside dest_dir (before optional .exe)
targets: tuple[str, ...] | None = None # limit installation to specific targets
WINDOWS_TARGETS = tuple(target for target in BINARY_TARGETS if "windows" in target)
BINARY_COMPONENTS = {
"codex": BinaryComponent(
artifact_prefix="codex",
@@ -49,6 +54,18 @@ BINARY_COMPONENTS = {
dest_dir="codex-responses-api-proxy",
binary_basename="codex-responses-api-proxy",
),
"codex-windows-sandbox-setup": BinaryComponent(
artifact_prefix="codex-windows-sandbox-setup",
dest_dir="codex",
binary_basename="codex-windows-sandbox-setup",
targets=WINDOWS_TARGETS,
),
"codex-command-runner": BinaryComponent(
artifact_prefix="codex-command-runner",
dest_dir="codex",
binary_basename="codex-command-runner",
targets=WINDOWS_TARGETS,
),
}
RG_TARGET_PLATFORM_PAIRS: list[tuple[str, str]] = [
@@ -62,6 +79,45 @@ RG_TARGET_PLATFORM_PAIRS: list[tuple[str, str]] = [
RG_TARGET_TO_PLATFORM = {target: platform for target, platform in RG_TARGET_PLATFORM_PAIRS}
DEFAULT_RG_TARGETS = [target for target, _ in RG_TARGET_PLATFORM_PAIRS]
# urllib.request.urlopen() defaults to no timeout (can hang indefinitely), which is painful in CI.
DOWNLOAD_TIMEOUT_SECS = 60
def _gha_enabled() -> bool:
# GitHub Actions supports "workflow commands" (e.g. ::group:: / ::error::) that make logs
# much easier to scan: groups collapse noisy sections and error annotations surface the
# failure in the UI without changing the actual exception/traceback output.
return os.environ.get("GITHUB_ACTIONS") == "true"
def _gha_escape(value: str) -> str:
# Workflow commands require percent/newline escaping.
return value.replace("%", "%25").replace("\r", "%0D").replace("\n", "%0A")
def _gha_error(*, title: str, message: str) -> None:
# Emit a GitHub Actions error annotation. This does not replace stdout/stderr logs; it just
# adds a prominent summary line to the job UI so the root cause is easier to spot.
if not _gha_enabled():
return
print(
f"::error title={_gha_escape(title)}::{_gha_escape(message)}",
flush=True,
)
@contextmanager
def _gha_group(title: str):
# Wrap a block in a collapsible log group on GitHub Actions. Outside of GHA this is a no-op
# so local output remains unchanged.
if _gha_enabled():
print(f"::group::{_gha_escape(title)}", flush=True)
try:
yield
finally:
if _gha_enabled():
print("::endgroup::", flush=True)
def parse_args() -> argparse.Namespace:
parser = argparse.ArgumentParser(description="Install native Codex binaries.")
@@ -79,7 +135,8 @@ def parse_args() -> argparse.Namespace:
choices=tuple(list(BINARY_COMPONENTS) + ["rg"]),
help=(
"Limit installation to the specified components."
" May be repeated. Defaults to 'codex' and 'rg'."
" May be repeated. Defaults to codex, codex-windows-sandbox-setup,"
" codex-command-runner, and rg."
),
)
parser.add_argument(
@@ -101,7 +158,12 @@ def main() -> int:
vendor_dir = codex_cli_root / VENDOR_DIR_NAME
vendor_dir.mkdir(parents=True, exist_ok=True)
components = args.components or ["codex", "rg"]
components = args.components or [
"codex",
"codex-windows-sandbox-setup",
"codex-command-runner",
"rg",
]
workflow_url = (args.workflow_url or DEFAULT_WORKFLOW_URL).strip()
if not workflow_url:
@@ -110,19 +172,20 @@ def main() -> int:
workflow_id = workflow_url.rstrip("/").split("/")[-1]
print(f"Downloading native artifacts from workflow {workflow_id}...")
with tempfile.TemporaryDirectory(prefix="codex-native-artifacts-") as artifacts_dir_str:
artifacts_dir = Path(artifacts_dir_str)
_download_artifacts(workflow_id, artifacts_dir)
install_binary_components(
artifacts_dir,
vendor_dir,
BINARY_TARGETS,
[name for name in components if name in BINARY_COMPONENTS],
)
with _gha_group(f"Download native artifacts from workflow {workflow_id}"):
with tempfile.TemporaryDirectory(prefix="codex-native-artifacts-") as artifacts_dir_str:
artifacts_dir = Path(artifacts_dir_str)
_download_artifacts(workflow_id, artifacts_dir)
install_binary_components(
artifacts_dir,
vendor_dir,
[BINARY_COMPONENTS[name] for name in components if name in BINARY_COMPONENTS],
)
if "rg" in components:
print("Fetching ripgrep binaries...")
fetch_rg(vendor_dir, DEFAULT_RG_TARGETS, manifest_path=RG_MANIFEST)
with _gha_group("Fetch ripgrep binaries"):
print("Fetching ripgrep binaries...")
fetch_rg(vendor_dir, DEFAULT_RG_TARGETS, manifest_path=RG_MANIFEST)
print(f"Installed native dependencies into {vendor_dir}")
return 0
@@ -183,7 +246,14 @@ def fetch_rg(
for future in as_completed(future_map):
target = future_map[future]
results[target] = future.result()
try:
results[target] = future.result()
except Exception as exc:
_gha_error(
title="ripgrep install failed",
message=f"target={target} error={exc!r}",
)
raise RuntimeError(f"Failed to install ripgrep for target {target}.") from exc
print(f" installed ripgrep for {target}")
return [results[target] for target in targets]
@@ -206,23 +276,19 @@ def _download_artifacts(workflow_id: str, dest_dir: Path) -> None:
def install_binary_components(
artifacts_dir: Path,
vendor_dir: Path,
targets: Iterable[str],
component_names: Sequence[str],
selected_components: Sequence[BinaryComponent],
) -> None:
selected_components = [BINARY_COMPONENTS[name] for name in component_names if name in BINARY_COMPONENTS]
if not selected_components:
return
targets = list(targets)
if not targets:
return
for component in selected_components:
component_targets = list(component.targets or BINARY_TARGETS)
print(
f"Installing {component.binary_basename} binaries for targets: "
+ ", ".join(targets)
+ ", ".join(component_targets)
)
max_workers = min(len(targets), max(1, (os.cpu_count() or 1)))
max_workers = min(len(component_targets), max(1, (os.cpu_count() or 1)))
with ThreadPoolExecutor(max_workers=max_workers) as executor:
futures = {
executor.submit(
@@ -232,7 +298,7 @@ def install_binary_components(
target,
component,
): target
for target in targets
for target in component_targets
}
for future in as_completed(futures):
installed_path = future.result()
@@ -285,6 +351,8 @@ def _fetch_single_rg(
url = providers[0]["url"]
archive_format = platform_info.get("format", "zst")
archive_member = platform_info.get("path")
digest = platform_info.get("digest")
expected_size = platform_info.get("size")
dest_dir = vendor_dir / target / "path"
dest_dir.mkdir(parents=True, exist_ok=True)
@@ -297,10 +365,32 @@ def _fetch_single_rg(
tmp_dir = Path(tmp_dir_str)
archive_filename = os.path.basename(urlparse(url).path)
download_path = tmp_dir / archive_filename
_download_file(url, download_path)
print(
f" downloading ripgrep for {target} ({platform_key}) from {url}",
flush=True,
)
try:
_download_file(url, download_path)
except Exception as exc:
_gha_error(
title="ripgrep download failed",
message=f"target={target} platform={platform_key} url={url} error={exc!r}",
)
raise RuntimeError(
"Failed to download ripgrep "
f"(target={target}, platform={platform_key}, format={archive_format}, "
f"expected_size={expected_size!r}, digest={digest!r}, url={url}, dest={download_path})."
) from exc
dest.unlink(missing_ok=True)
extract_archive(download_path, archive_format, archive_member, dest)
try:
extract_archive(download_path, archive_format, archive_member, dest)
except Exception as exc:
raise RuntimeError(
"Failed to extract ripgrep "
f"(target={target}, platform={platform_key}, format={archive_format}, "
f"member={archive_member!r}, url={url}, archive={download_path})."
) from exc
if not is_windows:
dest.chmod(0o755)
@@ -310,7 +400,9 @@ def _fetch_single_rg(
def _download_file(url: str, dest: Path) -> None:
dest.parent.mkdir(parents=True, exist_ok=True)
with urlopen(url) as response, open(dest, "wb") as out:
dest.unlink(missing_ok=True)
with urlopen(url, timeout=DOWNLOAD_TIMEOUT_SECS) as response, open(dest, "wb") as out:
shutil.copyfileobj(response, out)

View File

@@ -0,0 +1,6 @@
[advisories]
ignore = [
"RUSTSEC-2024-0388", # derivative 2.2.0 via starlark; upstream crate is unmaintained
"RUSTSEC-2025-0057", # fxhash 0.2.1 via starlark_map; upstream crate is unmaintained
"RUSTSEC-2024-0436", # paste 1.0.15 via starlark/ratatui; upstream crate is unmaintained
]

View File

@@ -0,0 +1,5 @@
[target.'cfg(all(windows, target_env = "msvc"))']
rustflags = ["-C", "link-arg=/STACK:8388608"]
[target.'cfg(all(windows, target_env = "gnu"))']
rustflags = ["-C", "link-arg=-Wl,--stack,8388608"]

View File

@@ -0,0 +1,13 @@
[profile.default]
# Do not increase, fix your test instead
slow-timeout = { period = "15s", terminate-after = 2 }
[[profile.default.overrides]]
# Do not add new tests here
filter = 'test(rmcp_client) | test(humanlike_typing_1000_chars_appears_live_no_placeholder)'
slow-timeout = { period = "1m", terminate-after = 4 }
[[profile.default.overrides]]
filter = 'test(approval_matrix_covers_all_modes)'
slow-timeout = { period = "30s", terminate-after = 2 }

View File

@@ -0,0 +1,26 @@
name: Cargo audit
on:
pull_request:
push:
branches:
- main
permissions:
contents: read
jobs:
audit:
runs-on: ubuntu-latest
defaults:
run:
working-directory: codex-rs
steps:
- uses: actions/checkout@v4
- uses: dtolnay/rust-toolchain@stable
- name: Install cargo-audit
uses: taiki-e/install-action@v2
with:
tool: cargo-audit
- name: Run cargo audit
run: cargo audit --deny warnings

1
codex-rs/BUILD.bazel Normal file
View File

@@ -0,0 +1 @@

2029
codex-rs/Cargo.lock generated

File diff suppressed because it is too large Load Diff

View File

@@ -5,6 +5,8 @@ members = [
"async-utils",
"app-server",
"app-server-protocol",
"app-server-test-client",
"debug-client",
"apply-patch",
"arg0",
"feedback",
@@ -15,28 +17,36 @@ members = [
"common",
"core",
"exec",
"exec-server",
"execpolicy",
"execpolicy-legacy",
"keyring-store",
"file-search",
"git-tooling",
"linux-sandbox",
"lmstudio",
"login",
"mcp-client",
"mcp-server",
"mcp-types",
"ollama",
"process-hardening",
"protocol",
"protocol-ts",
"rmcp-client",
"responses-api-proxy",
"stdio-to-uds",
"otel",
"tui",
"git-apply",
"tui2",
"utils/absolute-path",
"utils/cargo-bin",
"utils/git",
"utils/cache",
"utils/image",
"utils/json-to-toml",
"utils/readiness",
"utils/pty",
"utils/readiness",
"utils/string",
"codex-client",
"codex-api",
]
resolver = "2"
@@ -47,11 +57,13 @@ version = "0.0.0"
# crates created with `cargo new -w ...` automatically inherit the 2024
# edition.
edition = "2024"
license = "Apache-2.0"
[workspace.dependencies]
# Internal
app_test_support = { path = "app-server/tests/common" }
codex-ansi-escape = { path = "ansi-escape" }
codex-api = { path = "codex-api" }
codex-app-server = { path = "app-server" }
codex-app-server-protocol = { path = "app-server-protocol" }
codex-apply-patch = { path = "apply-patch" }
@@ -59,30 +71,39 @@ codex-arg0 = { path = "arg0" }
codex-async-utils = { path = "async-utils" }
codex-backend-client = { path = "backend-client" }
codex-chatgpt = { path = "chatgpt" }
codex-client = { path = "codex-client" }
codex-common = { path = "common" }
codex-core = { path = "core" }
codex-exec = { path = "exec" }
codex-execpolicy = { path = "execpolicy" }
codex-feedback = { path = "feedback" }
codex-file-search = { path = "file-search" }
codex-git-tooling = { path = "git-tooling" }
codex-git = { path = "utils/git" }
codex-keyring-store = { path = "keyring-store" }
codex-linux-sandbox = { path = "linux-sandbox" }
codex-lmstudio = { path = "lmstudio" }
codex-login = { path = "login" }
codex-mcp-client = { path = "mcp-client" }
codex-mcp-server = { path = "mcp-server" }
codex-ollama = { path = "ollama" }
codex-otel = { path = "otel" }
codex-process-hardening = { path = "process-hardening" }
codex-protocol = { path = "protocol" }
codex-protocol-ts = { path = "protocol-ts" }
codex-responses-api-proxy = { path = "responses-api-proxy" }
codex-rmcp-client = { path = "rmcp-client" }
codex-stdio-to-uds = { path = "stdio-to-uds" }
codex-tui = { path = "tui" }
codex-tui2 = { path = "tui2" }
codex-utils-absolute-path = { path = "utils/absolute-path" }
codex-utils-cache = { path = "utils/cache" }
codex-utils-cargo-bin = { path = "utils/cargo-bin" }
codex-utils-image = { path = "utils/image" }
codex-utils-json-to-toml = { path = "utils/json-to-toml" }
codex-utils-pty = { path = "utils/pty" }
codex-utils-readiness = { path = "utils/readiness" }
codex-utils-string = { path = "utils/string" }
codex-windows-sandbox = { path = "windows-sandbox-rs" }
core_test_support = { path = "core/tests/common" }
exec_server_test_support = { path = "exec-server/tests/common" }
mcp-types = { path = "mcp-types" }
mcp_test_support = { path = "mcp-server/tests/common" }
@@ -90,8 +111,7 @@ mcp_test_support = { path = "mcp-server/tests/common" }
allocative = "0.3.3"
ansi-to-tui = "7.0.0"
anyhow = "1"
arboard = "3"
askama = "0.12"
arboard = { version = "3", features = ["wayland-data-control"] }
assert_cmd = "2"
assert_matches = "1.5.0"
async-channel = "2.3.1"
@@ -100,6 +120,7 @@ async-trait = "0.1.89"
axum = { version = "0.8", default-features = false }
base64 = "0.22.1"
bytes = "1.10.1"
chardetng = "0.1.17"
chrono = "0.4.42"
clap = "4"
clap_complete = "4"
@@ -111,37 +132,42 @@ diffy = "0.4.2"
dirs = "6"
dotenvy = "0.15.7"
dunce = "1.0.4"
encoding_rs = "0.8.35"
env-flags = "0.1.1"
env_logger = "0.11.5"
escargot = "0.5"
eventsource-stream = "0.2.3"
futures = { version = "0.3", default-features = false }
icu_decimal = "2.0.0"
icu_locale_core = "2.0.0"
http = "1.3.1"
icu_decimal = "2.1"
icu_locale_core = "2.1"
icu_provider = { version = "2.1", features = ["sync"] }
ignore = "0.4.23"
image = { version = "^0.25.8", default-features = false }
indexmap = "2.6.0"
insta = "1.43.2"
image = { version = "^0.25.9", default-features = false }
include_dir = "0.7.4"
indexmap = "2.12.0"
insta = "1.46.0"
itertools = "0.14.0"
keyring = "3.6"
landlock = "0.4.1"
keyring = { version = "3.6", default-features = false }
landlock = "0.4.4"
lazy_static = "1"
libc = "0.2.175"
libc = "0.2.177"
log = "0.4"
lru = "0.16.3"
maplit = "1.0.2"
mime_guess = "2.0.5"
multimap = "0.10.0"
notify = "8.2.0"
nucleo-matcher = "0.3.1"
once_cell = "1.20.2"
openssl-sys = "*"
opentelemetry = "0.30.0"
opentelemetry-appender-tracing = "0.30.0"
opentelemetry-otlp = "0.30.0"
opentelemetry-semantic-conventions = "0.30.0"
opentelemetry_sdk = "0.30.0"
opentelemetry = "0.31.0"
opentelemetry-appender-tracing = "0.31.0"
opentelemetry-otlp = "0.31.0"
opentelemetry-semantic-conventions = "0.31.0"
opentelemetry_sdk = "0.31.0"
tracing-opentelemetry = "0.32.0"
os_info = "3.12.0"
owo-colors = "4.2.0"
paste = "1.0.15"
path-absolutize = "3.1.1"
pathdiff = "0.2"
portable-pty = "0.9.0"
@@ -150,47 +176,52 @@ pretty_assertions = "1.4.1"
pulldown-cmark = "0.10"
rand = "0.9"
ratatui = "0.29.0"
ratatui-core = "0.1.0"
ratatui-macros = "0.6.0"
regex-lite = "0.1.7"
regex = "1.12.2"
regex-lite = "0.1.8"
reqwest = "0.12"
rmcp = { version = "0.8.2", default-features = false }
rmcp = { version = "0.12.0", default-features = false }
schemars = "0.8.22"
seccompiler = "0.5.0"
sentry = "0.34.0"
sentry = "0.46.0"
serde = "1"
serde_json = "1"
serde_with = "3.14"
serde_with = "3.16"
serde_yaml = "0.9"
serial_test = "3.2.0"
sha1 = "0.10.6"
sha2 = "0.10"
shlex = "1.3.0"
similar = "2.7.0"
socket2 = "0.6.1"
starlark = "0.13.0"
strum = "0.27.2"
strum_macros = "0.27.2"
supports-color = "3.0.2"
sys-locale = "0.3.2"
tempfile = "3.23.0"
test-log = "0.2.18"
test-log = "0.2.19"
textwrap = "0.16.2"
thiserror = "2.0.16"
thiserror = "2.0.17"
time = "0.3"
tiny_http = "0.12"
tokio = "1"
tokio-stream = "0.1.17"
tokio-stream = "0.1.18"
tokio-test = "0.4"
tokio-util = "0.7.16"
toml = "0.9.5"
toml_edit = "0.23.4"
tonic = "0.13.1"
tracing = "0.1.41"
toml_edit = "0.24.0"
tracing = "0.1.43"
tracing-appender = "0.2.3"
tracing-subscriber = "0.3.20"
tracing-subscriber = "0.3.22"
tracing-test = "0.2.5"
tree-sitter = "0.25.10"
tree-sitter-bash = "0.25"
zstd = "0.13"
tree-sitter-highlight = "0.25.10"
ts-rs = "11"
tui-scrollbar = "0.2.1"
uds_windows = "1.1.0"
unicode-segmentation = "1.12.0"
unicode-width = "0.2"
@@ -201,9 +232,10 @@ vt100 = "0.16.2"
walkdir = "2.5.0"
webbrowser = "1.0"
which = "6"
wildmatch = "2.5.0"
wildmatch = "2.6.1"
wiremock = "0.6"
zeroize = "1.8.1"
zeroize = "1.8.2"
[workspace.lints]
rust = {}
@@ -246,7 +278,7 @@ unwrap_used = "deny"
# cargo-shear cannot see the platform-specific openssl-sys usage, so we
# silence the false positive here instead of deleting a real dependency.
[workspace.metadata.cargo-shear]
ignored = ["openssl-sys", "codex-utils-readiness"]
ignored = ["icu_provider", "openssl-sys", "codex-utils-readiness"]
[profile.release]
lto = "fat"
@@ -265,6 +297,7 @@ opt-level = 0
[patch.crates-io]
# Uncomment to debug local changes.
# ratatui = { path = "../../ratatui" }
crossterm = { git = "https://github.com/nornagon/crossterm", branch = "nornagon/color-query" }
ratatui = { git = "https://github.com/nornagon/ratatui", branch = "nornagon-v0.29.0-patch" }
# Uncomment to debug local changes.

View File

@@ -15,8 +15,8 @@ You can also install via Homebrew (`brew install --cask codex`) or download a pl
## Documentation quickstart
- First run with Codex? Follow the walkthrough in [`docs/getting-started.md`](../docs/getting-started.md) for prompts, keyboard shortcuts, and session management.
- Already shipping with Codex and want deeper control? Jump to [`docs/advanced.md`](../docs/advanced.md) and the configuration reference at [`docs/config.md`](../docs/config.md).
- First run with Codex? Start with [`docs/getting-started.md`](../docs/getting-started.md) (links to the walkthrough for prompts, keyboard shortcuts, and session management).
- Want deeper control? See [`docs/config.md`](../docs/config.md) and [`docs/install.md`](../docs/install.md).
## What's new in the Rust CLI
@@ -30,7 +30,7 @@ Codex supports a rich set of configuration options. Note that the Rust CLI uses
#### MCP client
Codex CLI functions as an MCP client that allows the Codex CLI and IDE extension to connect to MCP servers on startup. See the [`configuration documentation`](../docs/config.md#mcp_servers) for details.
Codex CLI functions as an MCP client that allows the Codex CLI and IDE extension to connect to MCP servers on startup. See the [`configuration documentation`](../docs/config.md#connecting-to-mcp-servers) for details.
#### MCP server (experimental)
@@ -46,7 +46,7 @@ Use `codex mcp` to add/list/get/remove MCP server launchers defined in `config.t
### Notifications
You can enable notifications by configuring a script that is run whenever the agent finishes a turn. The [notify documentation](../docs/config.md#notify) includes a detailed example that explains how to get desktop notifications via [terminal-notifier](https://github.com/julienXX/terminal-notifier) on macOS.
You can enable notifications by configuring a script that is run whenever the agent finishes a turn. The [notify documentation](../docs/config.md#notify) includes a detailed example that explains how to get desktop notifications via [terminal-notifier](https://github.com/julienXX/terminal-notifier) on macOS. When Codex detects that it is running under WSL 2 inside Windows Terminal (`WT_SESSION` is set), the TUI automatically falls back to native Windows toast notifications so approval prompts and completed turns surface even though Windows Terminal does not implement OSC 9.
### `codex exec` to run Codex programmatically/non-interactively
@@ -58,13 +58,16 @@ To test to see what happens when a command is run under the sandbox provided by
```
# macOS
codex sandbox macos [--full-auto] [COMMAND]...
codex sandbox macos [--full-auto] [--log-denials] [COMMAND]...
# Linux
codex sandbox linux [--full-auto] [COMMAND]...
# Windows
codex sandbox windows [--full-auto] [COMMAND]...
# Legacy aliases
codex debug seatbelt [--full-auto] [COMMAND]...
codex debug seatbelt [--full-auto] [--log-denials] [COMMAND]...
codex debug landlock [--full-auto] [COMMAND]...
```

View File

@@ -0,0 +1,6 @@
load("//:defs.bzl", "codex_rust_crate")
codex_rust_crate(
name = "ansi-escape",
crate_name = "codex_ansi_escape",
)

View File

@@ -1,7 +1,8 @@
[package]
edition = "2024"
name = "codex-ansi-escape"
version = { workspace = true }
version.workspace = true
edition.workspace = true
license.workspace = true
[lib]
name = "codex_ansi_escape"

View File

@@ -0,0 +1,6 @@
load("//:defs.bzl", "codex_rust_crate")
codex_rust_crate(
name = "app-server-protocol",
crate_name = "codex_app_server_protocol",
)

View File

@@ -1,7 +1,8 @@
[package]
edition = "2024"
name = "codex-app-server-protocol"
version = { workspace = true }
version.workspace = true
edition.workspace = true
license.workspace = true
[lib]
name = "codex_app_server_protocol"
@@ -14,11 +15,13 @@ workspace = true
anyhow = { workspace = true }
clap = { workspace = true, features = ["derive"] }
codex-protocol = { workspace = true }
paste = { workspace = true }
codex-utils-absolute-path = { workspace = true }
mcp-types = { workspace = true }
schemars = { workspace = true }
serde = { workspace = true, features = ["derive"] }
serde_json = { workspace = true }
strum_macros = { workspace = true }
thiserror = { workspace = true }
ts-rs = { workspace = true }
uuid = { workspace = true, features = ["serde", "v7"] }

View File

@@ -2,20 +2,25 @@ use crate::ClientNotification;
use crate::ClientRequest;
use crate::ServerNotification;
use crate::ServerRequest;
use crate::export_client_notification_schemas;
use crate::export_client_param_schemas;
use crate::export_client_response_schemas;
use crate::export_client_responses;
use crate::export_server_notification_schemas;
use crate::export_server_param_schemas;
use crate::export_server_response_schemas;
use crate::export_server_responses;
use anyhow::Context;
use anyhow::Result;
use anyhow::anyhow;
use codex_protocol::protocol::EventMsg;
use schemars::JsonSchema;
use schemars::schema::RootSchema;
use schemars::schema_for;
use serde::Serialize;
use serde_json::Map;
use serde_json::Value;
use std::collections::BTreeMap;
use std::collections::HashMap;
use std::collections::HashSet;
use std::ffi::OsStr;
use std::fs;
use std::io::Read;
@@ -26,92 +31,66 @@ use std::process::Command;
use ts_rs::TS;
const HEADER: &str = "// GENERATED CODE! DO NOT MODIFY BY HAND!\n\n";
const IGNORED_DEFINITIONS: &[&str] = &["Option<()>"];
macro_rules! for_each_schema_type {
($macro:ident) => {
$macro!(crate::RequestId);
$macro!(crate::JSONRPCMessage);
$macro!(crate::JSONRPCRequest);
$macro!(crate::JSONRPCNotification);
$macro!(crate::JSONRPCResponse);
$macro!(crate::JSONRPCError);
$macro!(crate::JSONRPCErrorError);
$macro!(crate::AddConversationListenerParams);
$macro!(crate::AddConversationSubscriptionResponse);
$macro!(crate::ApplyPatchApprovalParams);
$macro!(crate::ApplyPatchApprovalResponse);
$macro!(crate::ArchiveConversationParams);
$macro!(crate::ArchiveConversationResponse);
$macro!(crate::AuthMode);
$macro!(crate::AuthStatusChangeNotification);
$macro!(crate::CancelLoginChatGptParams);
$macro!(crate::CancelLoginChatGptResponse);
$macro!(crate::ClientInfo);
$macro!(crate::ClientNotification);
$macro!(crate::ClientRequest);
$macro!(crate::ConversationSummary);
$macro!(crate::ExecCommandApprovalParams);
$macro!(crate::ExecCommandApprovalResponse);
$macro!(crate::ExecOneOffCommandParams);
$macro!(crate::ExecOneOffCommandResponse);
$macro!(crate::FuzzyFileSearchParams);
$macro!(crate::FuzzyFileSearchResponse);
$macro!(crate::FuzzyFileSearchResult);
$macro!(crate::GetAuthStatusParams);
$macro!(crate::GetAuthStatusResponse);
$macro!(crate::GetUserAgentResponse);
$macro!(crate::GetUserSavedConfigResponse);
$macro!(crate::GitDiffToRemoteParams);
$macro!(crate::GitDiffToRemoteResponse);
$macro!(crate::GitSha);
$macro!(crate::InitializeParams);
$macro!(crate::InitializeResponse);
$macro!(crate::InputItem);
$macro!(crate::InterruptConversationParams);
$macro!(crate::InterruptConversationResponse);
$macro!(crate::ListConversationsParams);
$macro!(crate::ListConversationsResponse);
$macro!(crate::LoginApiKeyParams);
$macro!(crate::LoginApiKeyResponse);
$macro!(crate::LoginChatGptCompleteNotification);
$macro!(crate::LoginChatGptResponse);
$macro!(crate::LogoutChatGptParams);
$macro!(crate::LogoutChatGptResponse);
$macro!(crate::NewConversationParams);
$macro!(crate::NewConversationResponse);
$macro!(crate::Profile);
$macro!(crate::RemoveConversationListenerParams);
$macro!(crate::RemoveConversationSubscriptionResponse);
$macro!(crate::ResumeConversationParams);
$macro!(crate::ResumeConversationResponse);
$macro!(crate::SandboxSettings);
$macro!(crate::SendUserMessageParams);
$macro!(crate::SendUserMessageResponse);
$macro!(crate::SendUserTurnParams);
$macro!(crate::SendUserTurnResponse);
$macro!(crate::ServerNotification);
$macro!(crate::ServerRequest);
$macro!(crate::SessionConfiguredNotification);
$macro!(crate::SetDefaultModelParams);
$macro!(crate::SetDefaultModelResponse);
$macro!(crate::Tools);
$macro!(crate::UserInfoResponse);
$macro!(crate::UserSavedConfig);
$macro!(codex_protocol::protocol::EventMsg);
$macro!(codex_protocol::protocol::FileChange);
$macro!(codex_protocol::parse_command::ParsedCommand);
$macro!(codex_protocol::protocol::SandboxPolicy);
};
#[derive(Clone)]
pub struct GeneratedSchema {
namespace: Option<String>,
logical_name: String,
value: Value,
in_v1_dir: bool,
}
impl GeneratedSchema {
fn namespace(&self) -> Option<&str> {
self.namespace.as_deref()
}
fn logical_name(&self) -> &str {
&self.logical_name
}
fn value(&self) -> &Value {
&self.value
}
}
type JsonSchemaEmitter = fn(&Path) -> Result<GeneratedSchema>;
pub fn generate_types(out_dir: &Path, prettier: Option<&Path>) -> Result<()> {
generate_ts(out_dir, prettier)?;
generate_json(out_dir)?;
Ok(())
}
#[derive(Clone, Copy, Debug)]
pub struct GenerateTsOptions {
pub generate_indices: bool,
pub ensure_headers: bool,
pub run_prettier: bool,
}
impl Default for GenerateTsOptions {
fn default() -> Self {
Self {
generate_indices: true,
ensure_headers: true,
run_prettier: true,
}
}
}
pub fn generate_ts(out_dir: &Path, prettier: Option<&Path>) -> Result<()> {
generate_ts_with_options(out_dir, prettier, GenerateTsOptions::default())
}
pub fn generate_ts_with_options(
out_dir: &Path,
prettier: Option<&Path>,
options: GenerateTsOptions,
) -> Result<()> {
let v2_out_dir = out_dir.join("v2");
ensure_dir(out_dir)?;
ensure_dir(&v2_out_dir)?;
ClientRequest::export_all_to(out_dir)?;
export_client_responses(out_dir)?;
@@ -121,18 +100,34 @@ pub fn generate_ts(out_dir: &Path, prettier: Option<&Path>) -> Result<()> {
export_server_responses(out_dir)?;
ServerNotification::export_all_to(out_dir)?;
generate_index_ts(out_dir)?;
let ts_files = ts_files_in(out_dir)?;
for file in &ts_files {
prepend_header_if_missing(file)?;
if options.generate_indices {
generate_index_ts(out_dir)?;
generate_index_ts(&v2_out_dir)?;
}
if let Some(prettier_bin) = prettier
// Ensure our header is present on all TS files (root + subdirs like v2/).
let mut ts_files = Vec::new();
let should_collect_ts_files =
options.ensure_headers || (options.run_prettier && prettier.is_some());
if should_collect_ts_files {
ts_files = ts_files_in_recursive(out_dir)?;
}
if options.ensure_headers {
for file in &ts_files {
prepend_header_if_missing(file)?;
}
}
// Optionally run Prettier on all generated TS files.
if options.run_prettier
&& let Some(prettier_bin) = prettier
&& !ts_files.is_empty()
{
let status = Command::new(prettier_bin)
.arg("--write")
.arg("--log-level")
.arg("warn")
.args(ts_files.iter().map(|p| p.as_os_str()))
.status()
.with_context(|| format!("Failed to invoke Prettier at {}", prettier_bin.display()))?;
@@ -146,59 +141,114 @@ pub fn generate_ts(out_dir: &Path, prettier: Option<&Path>) -> Result<()> {
pub fn generate_json(out_dir: &Path) -> Result<()> {
ensure_dir(out_dir)?;
let mut bundle: BTreeMap<String, RootSchema> = BTreeMap::new();
let envelope_emitters: &[JsonSchemaEmitter] = &[
|d| write_json_schema_with_return::<crate::RequestId>(d, "RequestId"),
|d| write_json_schema_with_return::<crate::JSONRPCMessage>(d, "JSONRPCMessage"),
|d| write_json_schema_with_return::<crate::JSONRPCRequest>(d, "JSONRPCRequest"),
|d| write_json_schema_with_return::<crate::JSONRPCNotification>(d, "JSONRPCNotification"),
|d| write_json_schema_with_return::<crate::JSONRPCResponse>(d, "JSONRPCResponse"),
|d| write_json_schema_with_return::<crate::JSONRPCError>(d, "JSONRPCError"),
|d| write_json_schema_with_return::<crate::JSONRPCErrorError>(d, "JSONRPCErrorError"),
|d| write_json_schema_with_return::<crate::ClientRequest>(d, "ClientRequest"),
|d| write_json_schema_with_return::<crate::ServerRequest>(d, "ServerRequest"),
|d| write_json_schema_with_return::<crate::ClientNotification>(d, "ClientNotification"),
|d| write_json_schema_with_return::<crate::ServerNotification>(d, "ServerNotification"),
|d| write_json_schema_with_return::<EventMsg>(d, "EventMsg"),
];
macro_rules! add_schema {
($ty:path) => {{
let name = type_basename(stringify!($ty));
let schema = write_json_schema_with_return::<$ty>(out_dir, &name)?;
bundle.insert(name, schema);
}};
let mut schemas: Vec<GeneratedSchema> = Vec::new();
for emit in envelope_emitters {
schemas.push(emit(out_dir)?);
}
for_each_schema_type!(add_schema);
schemas.extend(export_client_param_schemas(out_dir)?);
schemas.extend(export_client_response_schemas(out_dir)?);
schemas.extend(export_server_param_schemas(out_dir)?);
schemas.extend(export_server_response_schemas(out_dir)?);
schemas.extend(export_client_notification_schemas(out_dir)?);
schemas.extend(export_server_notification_schemas(out_dir)?);
export_client_response_schemas(out_dir)?;
export_server_response_schemas(out_dir)?;
let bundle = build_schema_bundle(schemas)?;
write_pretty_json(
out_dir.join("codex_app_server_protocol.schemas.json"),
&bundle,
)?;
let mut definitions = Map::new();
Ok(())
}
fn build_schema_bundle(schemas: Vec<GeneratedSchema>) -> Result<Value> {
const SPECIAL_DEFINITIONS: &[&str] = &[
"ClientNotification",
"ClientRequest",
"EventMsg",
"FileChange",
"InputItem",
"ParsedCommand",
"SandboxPolicy",
"ServerNotification",
"ServerRequest",
];
for (name, schema) in bundle {
let mut schema_value = serde_json::to_value(schema)?;
if let Value::Object(ref mut obj) = schema_value {
if let Some(defs) = obj.remove("definitions")
&& let Value::Object(defs_obj) = defs
{
for (def_name, def_schema) in defs_obj {
if !SPECIAL_DEFINITIONS.contains(&def_name.as_str()) {
definitions.insert(def_name, def_schema);
}
}
}
let namespaced_types = collect_namespaced_types(&schemas);
let mut definitions = Map::new();
if let Some(Value::Array(one_of)) = obj.get_mut("oneOf") {
for variant in one_of.iter_mut() {
if let Some(variant_name) = variant_definition_name(&name, variant)
&& let Value::Object(variant_obj) = variant
for schema in schemas {
let GeneratedSchema {
namespace,
logical_name,
mut value,
in_v1_dir,
} = schema;
if IGNORED_DEFINITIONS.contains(&logical_name.as_str()) {
continue;
}
if let Some(ref ns) = namespace {
rewrite_refs_to_namespace(&mut value, ns);
}
let mut forced_namespace_refs: Vec<(String, String)> = Vec::new();
if let Value::Object(ref mut obj) = value
&& let Some(defs) = obj.remove("definitions")
&& let Value::Object(defs_obj) = defs
{
for (def_name, mut def_schema) in defs_obj {
if IGNORED_DEFINITIONS.contains(&def_name.as_str()) {
continue;
}
if SPECIAL_DEFINITIONS.contains(&def_name.as_str()) {
continue;
}
annotate_schema(&mut def_schema, Some(def_name.as_str()));
let target_namespace = match namespace {
Some(ref ns) => Some(ns.clone()),
None => namespace_for_definition(&def_name, &namespaced_types)
.cloned()
.filter(|_| !in_v1_dir),
};
if let Some(ref ns) = target_namespace {
if namespace.as_deref() == Some(ns.as_str()) {
rewrite_refs_to_namespace(&mut def_schema, ns);
insert_into_namespace(&mut definitions, ns, def_name.clone(), def_schema)?;
} else if !forced_namespace_refs
.iter()
.any(|(name, existing_ns)| name == &def_name && existing_ns == ns)
{
variant_obj.insert("title".into(), Value::String(variant_name));
forced_namespace_refs.push((def_name.clone(), ns.clone()));
}
} else {
definitions.insert(def_name, def_schema);
}
}
}
definitions.insert(name, schema_value);
for (name, ns) in forced_namespace_refs {
rewrite_named_ref_to_namespace(&mut value, &ns, &name);
}
if let Some(ref ns) = namespace {
insert_into_namespace(&mut definitions, ns, logical_name.clone(), value)?;
} else {
definitions.insert(logical_name, value);
}
}
let mut root = Map::new();
@@ -213,30 +263,69 @@ pub fn generate_json(out_dir: &Path) -> Result<()> {
root.insert("type".to_string(), Value::String("object".into()));
root.insert("definitions".to_string(), Value::Object(definitions));
write_pretty_json(
out_dir.join("codex_app_server_protocol.schemas.json"),
&Value::Object(root),
)?;
Ok(())
Ok(Value::Object(root))
}
fn write_json_schema_with_return<T>(out_dir: &Path, name: &str) -> Result<RootSchema>
fn insert_into_namespace(
definitions: &mut Map<String, Value>,
namespace: &str,
name: String,
schema: Value,
) -> Result<()> {
let entry = definitions
.entry(namespace.to_string())
.or_insert_with(|| Value::Object(Map::new()));
match entry {
Value::Object(map) => {
map.insert(name, schema);
Ok(())
}
_ => Err(anyhow!("expected namespace {namespace} to be an object")),
}
}
fn write_json_schema_with_return<T>(out_dir: &Path, name: &str) -> Result<GeneratedSchema>
where
T: JsonSchema,
{
let file_stem = name.trim();
let schema = schema_for!(T);
write_pretty_json(out_dir.join(format!("{file_stem}.json")), &schema)
.with_context(|| format!("Failed to write JSON schema for {file_stem}"))?;
Ok(schema)
let mut schema_value = serde_json::to_value(schema)?;
annotate_schema(&mut schema_value, Some(file_stem));
// If the name looks like a namespaced path (e.g., "v2::Type"), mirror
// the TypeScript layout and write to out_dir/v2/Type.json. Otherwise
// write alongside the legacy files.
let (raw_namespace, logical_name) = split_namespace(file_stem);
let out_path = if let Some(ns) = raw_namespace {
let dir = out_dir.join(ns);
ensure_dir(&dir)?;
dir.join(format!("{logical_name}.json"))
} else {
out_dir.join(format!("{file_stem}.json"))
};
if !IGNORED_DEFINITIONS.contains(&logical_name) {
write_pretty_json(out_path, &schema_value)
.with_context(|| format!("Failed to write JSON schema for {file_stem}"))?;
}
let namespace = match raw_namespace {
Some("v1") | None => None,
Some(ns) => Some(ns.to_string()),
};
Ok(GeneratedSchema {
in_v1_dir: raw_namespace == Some("v1"),
namespace,
logical_name: logical_name.to_string(),
value: schema_value,
})
}
pub(crate) fn write_json_schema<T>(out_dir: &Path, name: &str) -> Result<()>
pub(crate) fn write_json_schema<T>(out_dir: &Path, name: &str) -> Result<GeneratedSchema>
where
T: JsonSchema,
{
write_json_schema_with_return::<T>(out_dir, name).map(|_| ())
write_json_schema_with_return::<T>(out_dir, name)
}
fn write_pretty_json(path: PathBuf, value: &impl Serialize) -> Result<()> {
@@ -245,13 +334,73 @@ fn write_pretty_json(path: PathBuf, value: &impl Serialize) -> Result<()> {
fs::write(&path, json).with_context(|| format!("Failed to write {}", path.display()))?;
Ok(())
}
fn type_basename(type_path: &str) -> String {
type_path
.rsplit_once("::")
.map(|(_, name)| name)
.unwrap_or(type_path)
.trim()
.to_string()
/// Split a fully-qualified type name like "v2::Type" into its namespace and logical name.
fn split_namespace(name: &str) -> (Option<&str>, &str) {
name.split_once("::")
.map_or((None, name), |(ns, rest)| (Some(ns), rest))
}
/// Recursively rewrite $ref values that point at "#/definitions/..." so that
/// they point to a namespaced location under the bundle.
fn rewrite_refs_to_namespace(value: &mut Value, ns: &str) {
match value {
Value::Object(obj) => {
if let Some(Value::String(r)) = obj.get_mut("$ref")
&& let Some(suffix) = r.strip_prefix("#/definitions/")
{
let prefix = format!("{ns}/");
if !suffix.starts_with(&prefix) {
*r = format!("#/definitions/{ns}/{suffix}");
}
}
for v in obj.values_mut() {
rewrite_refs_to_namespace(v, ns);
}
}
Value::Array(items) => {
for v in items.iter_mut() {
rewrite_refs_to_namespace(v, ns);
}
}
_ => {}
}
}
fn collect_namespaced_types(schemas: &[GeneratedSchema]) -> HashMap<String, String> {
let mut types = HashMap::new();
for schema in schemas {
if let Some(ns) = schema.namespace() {
types
.entry(schema.logical_name().to_string())
.or_insert_with(|| ns.to_string());
if let Some(Value::Object(defs)) = schema.value().get("definitions") {
for key in defs.keys() {
types.entry(key.clone()).or_insert_with(|| ns.to_string());
}
}
if let Some(Value::Object(defs)) = schema.value().get("$defs") {
for key in defs.keys() {
types.entry(key.clone()).or_insert_with(|| ns.to_string());
}
}
}
}
types
}
fn namespace_for_definition<'a>(
name: &str,
types: &'a HashMap<String, String>,
) -> Option<&'a String> {
if let Some(ns) = types.get(name) {
return Some(ns);
}
let trimmed = name.trim_end_matches(|c: char| c.is_ascii_digit());
if trimmed != name {
return types.get(trimmed);
}
None
}
fn variant_definition_name(base: &str, variant: &Value) -> Option<String> {
@@ -273,14 +422,6 @@ fn variant_definition_name(base: &str, variant: &Value) -> Option<String> {
});
}
if let Some(mode_literal) = literal_from_property(props, "mode") {
let pascal = to_pascal_case(mode_literal);
return Some(match base {
"SandboxPolicy" => format!("{pascal}SandboxPolicy"),
_ => format!("{pascal}{base}"),
});
}
if props.len() == 1
&& let Some(key) = props.keys().next()
{
@@ -301,11 +442,147 @@ fn variant_definition_name(base: &str, variant: &Value) -> Option<String> {
}
fn literal_from_property<'a>(props: &'a Map<String, Value>, key: &str) -> Option<&'a str> {
props
.get(key)
.and_then(|value| value.get("enum"))
.and_then(Value::as_array)
.and_then(|arr| arr.first())
props.get(key).and_then(string_literal)
}
fn string_literal(value: &Value) -> Option<&str> {
value.get("const").and_then(Value::as_str).or_else(|| {
value
.get("enum")
.and_then(Value::as_array)
.and_then(|arr| arr.first())
.and_then(Value::as_str)
})
}
fn annotate_schema(value: &mut Value, base: Option<&str>) {
match value {
Value::Object(map) => annotate_object(map, base),
Value::Array(items) => {
for item in items {
annotate_schema(item, base);
}
}
_ => {}
}
}
fn annotate_object(map: &mut Map<String, Value>, base: Option<&str>) {
let owner = map.get("title").and_then(Value::as_str).map(str::to_owned);
if let Some(owner) = owner.as_deref()
&& let Some(Value::Object(props)) = map.get_mut("properties")
{
set_discriminator_titles(props, owner);
}
if let Some(Value::Array(variants)) = map.get_mut("oneOf") {
annotate_variant_list(variants, base);
}
if let Some(Value::Array(variants)) = map.get_mut("anyOf") {
annotate_variant_list(variants, base);
}
if let Some(Value::Object(defs)) = map.get_mut("definitions") {
for (name, schema) in defs.iter_mut() {
annotate_schema(schema, Some(name.as_str()));
}
}
if let Some(Value::Object(defs)) = map.get_mut("$defs") {
for (name, schema) in defs.iter_mut() {
annotate_schema(schema, Some(name.as_str()));
}
}
if let Some(Value::Object(props)) = map.get_mut("properties") {
for value in props.values_mut() {
annotate_schema(value, base);
}
}
if let Some(items) = map.get_mut("items") {
annotate_schema(items, base);
}
if let Some(additional) = map.get_mut("additionalProperties") {
annotate_schema(additional, base);
}
for (key, child) in map.iter_mut() {
match key.as_str() {
"oneOf"
| "anyOf"
| "definitions"
| "$defs"
| "properties"
| "items"
| "additionalProperties" => {}
_ => annotate_schema(child, base),
}
}
}
fn annotate_variant_list(variants: &mut [Value], base: Option<&str>) {
let mut seen = HashSet::new();
for variant in variants.iter() {
if let Some(name) = variant_title(variant) {
seen.insert(name.to_owned());
}
}
for variant in variants.iter_mut() {
let mut variant_name = variant_title(variant).map(str::to_owned);
if variant_name.is_none()
&& let Some(base_name) = base
&& let Some(name) = variant_definition_name(base_name, variant)
{
let mut candidate = name.clone();
let mut index = 2;
while seen.contains(&candidate) {
candidate = format!("{name}{index}");
index += 1;
}
if let Some(obj) = variant.as_object_mut() {
obj.insert("title".into(), Value::String(candidate.clone()));
}
seen.insert(candidate.clone());
variant_name = Some(candidate);
}
if let Some(name) = variant_name.as_deref()
&& let Some(obj) = variant.as_object_mut()
&& let Some(Value::Object(props)) = obj.get_mut("properties")
{
set_discriminator_titles(props, name);
}
annotate_schema(variant, base);
}
}
const DISCRIMINATOR_KEYS: &[&str] = &["type", "method", "mode", "status", "role", "reason"];
fn set_discriminator_titles(props: &mut Map<String, Value>, owner: &str) {
for key in DISCRIMINATOR_KEYS {
if let Some(prop_schema) = props.get_mut(*key)
&& string_literal(prop_schema).is_some()
&& let Value::Object(prop_obj) = prop_schema
{
if prop_obj.contains_key("title") {
continue;
}
let suffix = to_pascal_case(key);
prop_obj.insert("title".into(), Value::String(format!("{owner}{suffix}")));
}
}
}
fn variant_title(value: &Value) -> Option<&str> {
value
.as_object()
.and_then(|obj| obj.get("title"))
.and_then(Value::as_str)
}
@@ -335,6 +612,33 @@ fn ensure_dir(dir: &Path) -> Result<()> {
.with_context(|| format!("Failed to create output directory {}", dir.display()))
}
fn rewrite_named_ref_to_namespace(value: &mut Value, ns: &str, name: &str) {
let direct = format!("#/definitions/{name}");
let prefixed = format!("{direct}/");
let replacement = format!("#/definitions/{ns}/{name}");
let replacement_prefixed = format!("{replacement}/");
match value {
Value::Object(obj) => {
if let Some(Value::String(reference)) = obj.get_mut("$ref") {
if reference == &direct {
*reference = replacement;
} else if let Some(rest) = reference.strip_prefix(&prefixed) {
*reference = format!("{replacement_prefixed}{rest}");
}
}
for child in obj.values_mut() {
rewrite_named_ref_to_namespace(child, ns, name);
}
}
Value::Array(items) => {
for child in items {
rewrite_named_ref_to_namespace(child, ns, name);
}
}
_ => {}
}
}
fn prepend_header_if_missing(path: &Path) -> Result<()> {
let mut content = String::new();
{
@@ -372,6 +676,28 @@ fn ts_files_in(dir: &Path) -> Result<Vec<PathBuf>> {
Ok(files)
}
fn ts_files_in_recursive(dir: &Path) -> Result<Vec<PathBuf>> {
let mut files = Vec::new();
let mut stack = vec![dir.to_path_buf()];
while let Some(d) = stack.pop() {
for entry in
fs::read_dir(&d).with_context(|| format!("Failed to read dir {}", d.display()))?
{
let entry = entry?;
let path = entry.path();
if path.is_dir() {
stack.push(path);
} else if path.is_file() && path.extension() == Some(OsStr::new("ts")) {
files.push(path);
}
}
}
files.sort();
Ok(files)
}
/// Generate an index.ts file that re-exports all generated types.
/// This allows consumers to import all types from a single file.
fn generate_index_ts(out_dir: &Path) -> Result<PathBuf> {
let mut entries: Vec<String> = Vec::new();
let mut stems: Vec<String> = ts_files_in(out_dir)?
@@ -388,6 +714,14 @@ fn generate_index_ts(out_dir: &Path) -> Result<PathBuf> {
entries.push(format!("export type {{ {name} }} from \"./{name}\";\n"));
}
// If this is the root out_dir and a ./v2 folder exists with TS files,
// expose it as a namespace to avoid symbol collisions at the root.
let v2_dir = out_dir.join("v2");
let has_v2_ts = ts_files_in(&v2_dir).map(|v| !v.is_empty()).unwrap_or(false);
if has_v2_ts {
entries.push("export * as v2 from \"./v2\";\n".to_string());
}
let mut content =
String::with_capacity(HEADER.len() + entries.iter().map(String::len).sum::<usize>());
content.push_str(HEADER);
@@ -402,3 +736,211 @@ fn generate_index_ts(out_dir: &Path) -> Result<PathBuf> {
.with_context(|| format!("Failed to write {}", index_path.display()))?;
Ok(index_path)
}
#[cfg(test)]
mod tests {
use super::*;
use anyhow::Result;
use std::collections::BTreeSet;
use std::fs;
use std::path::PathBuf;
use uuid::Uuid;
#[test]
fn generated_ts_has_no_optional_nullable_fields() -> Result<()> {
// Assert that there are no types of the form "?: T | null" in the generated TS files.
let output_dir = std::env::temp_dir().join(format!("codex_ts_types_{}", Uuid::now_v7()));
fs::create_dir(&output_dir)?;
struct TempDirGuard(PathBuf);
impl Drop for TempDirGuard {
fn drop(&mut self) {
let _ = fs::remove_dir_all(&self.0);
}
}
let _guard = TempDirGuard(output_dir.clone());
// Avoid doing more work than necessary to keep the test from timing out.
let options = GenerateTsOptions {
generate_indices: false,
ensure_headers: false,
run_prettier: false,
};
generate_ts_with_options(&output_dir, None, options)?;
let mut undefined_offenders = Vec::new();
let mut optional_nullable_offenders = BTreeSet::new();
let mut stack = vec![output_dir];
while let Some(dir) = stack.pop() {
for entry in fs::read_dir(&dir)? {
let entry = entry?;
let path = entry.path();
if path.is_dir() {
stack.push(path);
continue;
}
if matches!(path.extension().and_then(|ext| ext.to_str()), Some("ts")) {
let contents = fs::read_to_string(&path)?;
if contents.contains("| undefined") {
undefined_offenders.push(path.clone());
}
const SKIP_PREFIXES: &[&str] = &[
"const ",
"let ",
"var ",
"export const ",
"export let ",
"export var ",
];
let mut search_start = 0;
while let Some(idx) = contents[search_start..].find("| null") {
let abs_idx = search_start + idx;
// Find the property-colon for this field by scanning forward
// from the start of the segment and ignoring nested braces,
// brackets, and parens. This avoids colons inside nested
// type literals like `{ [k in string]?: string }`.
let line_start_idx =
contents[..abs_idx].rfind('\n').map(|i| i + 1).unwrap_or(0);
let mut segment_start_idx = line_start_idx;
if let Some(rel_idx) = contents[line_start_idx..abs_idx].rfind(',') {
segment_start_idx = segment_start_idx.max(line_start_idx + rel_idx + 1);
}
if let Some(rel_idx) = contents[line_start_idx..abs_idx].rfind('{') {
segment_start_idx = segment_start_idx.max(line_start_idx + rel_idx + 1);
}
if let Some(rel_idx) = contents[line_start_idx..abs_idx].rfind('}') {
segment_start_idx = segment_start_idx.max(line_start_idx + rel_idx + 1);
}
// Scan forward for the colon that separates the field name from its type.
let mut level_brace = 0_i32;
let mut level_brack = 0_i32;
let mut level_paren = 0_i32;
let mut in_single = false;
let mut in_double = false;
let mut escape = false;
let mut prop_colon_idx = None;
for (i, ch) in contents[segment_start_idx..abs_idx].char_indices() {
let idx_abs = segment_start_idx + i;
if escape {
escape = false;
continue;
}
match ch {
'\\' => {
// Only treat as escape when inside a string.
if in_single || in_double {
escape = true;
}
}
'\'' => {
if !in_double {
in_single = !in_single;
}
}
'"' => {
if !in_single {
in_double = !in_double;
}
}
'{' if !in_single && !in_double => level_brace += 1,
'}' if !in_single && !in_double => level_brace -= 1,
'[' if !in_single && !in_double => level_brack += 1,
']' if !in_single && !in_double => level_brack -= 1,
'(' if !in_single && !in_double => level_paren += 1,
')' if !in_single && !in_double => level_paren -= 1,
':' if !in_single
&& !in_double
&& level_brace == 0
&& level_brack == 0
&& level_paren == 0 =>
{
prop_colon_idx = Some(idx_abs);
break;
}
_ => {}
}
}
let Some(colon_idx) = prop_colon_idx else {
search_start = abs_idx + 5;
continue;
};
let mut field_prefix = contents[segment_start_idx..colon_idx].trim();
if field_prefix.is_empty() {
search_start = abs_idx + 5;
continue;
}
if let Some(comment_idx) = field_prefix.rfind("*/") {
field_prefix = field_prefix[comment_idx + 2..].trim_start();
}
if field_prefix.is_empty() {
search_start = abs_idx + 5;
continue;
}
if SKIP_PREFIXES
.iter()
.any(|prefix| field_prefix.starts_with(prefix))
{
search_start = abs_idx + 5;
continue;
}
if field_prefix.contains('(') {
search_start = abs_idx + 5;
continue;
}
// If the last non-whitespace before ':' is '?', then this is an
// optional field with a nullable type (i.e., "?: T | null"),
// which we explicitly disallow.
if field_prefix.chars().rev().find(|c| !c.is_whitespace()) == Some('?') {
let line_number =
contents[..abs_idx].chars().filter(|c| *c == '\n').count() + 1;
let offending_line_end = contents[line_start_idx..]
.find('\n')
.map(|i| line_start_idx + i)
.unwrap_or(contents.len());
let offending_snippet =
contents[line_start_idx..offending_line_end].trim();
optional_nullable_offenders.insert(format!(
"{}:{}: {offending_snippet}",
path.display(),
line_number
));
}
search_start = abs_idx + 5;
}
}
}
}
assert!(
undefined_offenders.is_empty(),
"Generated TypeScript still includes unions with `undefined` in {undefined_offenders:?}"
);
// If this assertion fails, it means a field was generated as
// "?: T | null" — i.e., both optional (undefined) and nullable (null).
// We only want either "?: T" or ": T | null".
assert!(
optional_nullable_offenders.is_empty(),
"Generated TypeScript has optional fields with nullable types (disallowed '?: T | null'), add #[ts(optional)] to fix:\n{optional_nullable_offenders:?}"
);
Ok(())
}
}

View File

@@ -34,6 +34,7 @@ pub struct JSONRPCRequest {
pub id: RequestId,
pub method: String,
#[serde(default, skip_serializing_if = "Option::is_none")]
#[ts(optional)]
pub params: Option<serde_json::Value>,
}
@@ -42,6 +43,7 @@ pub struct JSONRPCRequest {
pub struct JSONRPCNotification {
pub method: String,
#[serde(default, skip_serializing_if = "Option::is_none")]
#[ts(optional)]
pub params: Option<serde_json::Value>,
}
@@ -63,6 +65,7 @@ pub struct JSONRPCError {
pub struct JSONRPCErrorError {
pub code: i64,
#[serde(default, skip_serializing_if = "Option::is_none")]
#[ts(optional)]
pub data: Option<serde_json::Value>,
pub message: String,
}

View File

@@ -6,4 +6,7 @@ pub use export::generate_json;
pub use export::generate_ts;
pub use export::generate_types;
pub use jsonrpc_lite::*;
pub use protocol::*;
pub use protocol::common::*;
pub use protocol::thread_history::*;
pub use protocol::v1::*;
pub use protocol::v2::*;

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,871 @@
use std::path::Path;
use crate::JSONRPCNotification;
use crate::JSONRPCRequest;
use crate::RequestId;
use crate::export::GeneratedSchema;
use crate::export::write_json_schema;
use crate::protocol::v1;
use crate::protocol::v2;
use schemars::JsonSchema;
use serde::Deserialize;
use serde::Serialize;
use strum_macros::Display;
use ts_rs::TS;
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema, TS)]
#[ts(type = "string")]
pub struct GitSha(pub String);
impl GitSha {
pub fn new(sha: &str) -> Self {
Self(sha.to_string())
}
}
#[derive(Serialize, Deserialize, Debug, Clone, Copy, PartialEq, Eq, Display, JsonSchema, TS)]
#[serde(rename_all = "lowercase")]
pub enum AuthMode {
ApiKey,
ChatGPT,
}
/// Generates an `enum ClientRequest` where each variant is a request that the
/// client can send to the server. Each variant has associated `params` and
/// `response` types. Also generates a `export_client_responses()` function to
/// export all response types to TypeScript.
macro_rules! client_request_definitions {
(
$(
$(#[$variant_meta:meta])*
$variant:ident $(=> $wire:literal)? {
params: $(#[$params_meta:meta])* $params:ty,
response: $response:ty,
}
),* $(,)?
) => {
/// Request from the client to the server.
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)]
#[serde(tag = "method", rename_all = "camelCase")]
pub enum ClientRequest {
$(
$(#[$variant_meta])*
$(#[serde(rename = $wire)] #[ts(rename = $wire)])?
$variant {
#[serde(rename = "id")]
request_id: RequestId,
$(#[$params_meta])*
params: $params,
},
)*
}
pub fn export_client_responses(
out_dir: &::std::path::Path,
) -> ::std::result::Result<(), ::ts_rs::ExportError> {
$(
<$response as ::ts_rs::TS>::export_all_to(out_dir)?;
)*
Ok(())
}
#[allow(clippy::vec_init_then_push)]
pub fn export_client_response_schemas(
out_dir: &::std::path::Path,
) -> ::anyhow::Result<Vec<GeneratedSchema>> {
let mut schemas = Vec::new();
$(
schemas.push(write_json_schema::<$response>(out_dir, stringify!($response))?);
)*
Ok(schemas)
}
#[allow(clippy::vec_init_then_push)]
pub fn export_client_param_schemas(
out_dir: &::std::path::Path,
) -> ::anyhow::Result<Vec<GeneratedSchema>> {
let mut schemas = Vec::new();
$(
schemas.push(write_json_schema::<$params>(out_dir, stringify!($params))?);
)*
Ok(schemas)
}
};
}
client_request_definitions! {
Initialize {
params: v1::InitializeParams,
response: v1::InitializeResponse,
},
/// NEW APIs
// Thread lifecycle
ThreadStart => "thread/start" {
params: v2::ThreadStartParams,
response: v2::ThreadStartResponse,
},
ThreadResume => "thread/resume" {
params: v2::ThreadResumeParams,
response: v2::ThreadResumeResponse,
},
ThreadFork => "thread/fork" {
params: v2::ThreadForkParams,
response: v2::ThreadForkResponse,
},
ThreadArchive => "thread/archive" {
params: v2::ThreadArchiveParams,
response: v2::ThreadArchiveResponse,
},
ThreadRollback => "thread/rollback" {
params: v2::ThreadRollbackParams,
response: v2::ThreadRollbackResponse,
},
ThreadList => "thread/list" {
params: v2::ThreadListParams,
response: v2::ThreadListResponse,
},
ThreadLoadedList => "thread/loaded/list" {
params: v2::ThreadLoadedListParams,
response: v2::ThreadLoadedListResponse,
},
SkillsList => "skills/list" {
params: v2::SkillsListParams,
response: v2::SkillsListResponse,
},
TurnStart => "turn/start" {
params: v2::TurnStartParams,
response: v2::TurnStartResponse,
},
TurnInterrupt => "turn/interrupt" {
params: v2::TurnInterruptParams,
response: v2::TurnInterruptResponse,
},
ReviewStart => "review/start" {
params: v2::ReviewStartParams,
response: v2::ReviewStartResponse,
},
ModelList => "model/list" {
params: v2::ModelListParams,
response: v2::ModelListResponse,
},
McpServerOauthLogin => "mcpServer/oauth/login" {
params: v2::McpServerOauthLoginParams,
response: v2::McpServerOauthLoginResponse,
},
McpServerStatusList => "mcpServerStatus/list" {
params: v2::ListMcpServerStatusParams,
response: v2::ListMcpServerStatusResponse,
},
LoginAccount => "account/login/start" {
params: v2::LoginAccountParams,
response: v2::LoginAccountResponse,
},
CancelLoginAccount => "account/login/cancel" {
params: v2::CancelLoginAccountParams,
response: v2::CancelLoginAccountResponse,
},
LogoutAccount => "account/logout" {
params: #[ts(type = "undefined")] #[serde(skip_serializing_if = "Option::is_none")] Option<()>,
response: v2::LogoutAccountResponse,
},
GetAccountRateLimits => "account/rateLimits/read" {
params: #[ts(type = "undefined")] #[serde(skip_serializing_if = "Option::is_none")] Option<()>,
response: v2::GetAccountRateLimitsResponse,
},
FeedbackUpload => "feedback/upload" {
params: v2::FeedbackUploadParams,
response: v2::FeedbackUploadResponse,
},
/// Execute a command (argv vector) under the server's sandbox.
OneOffCommandExec => "command/exec" {
params: v2::CommandExecParams,
response: v2::CommandExecResponse,
},
ConfigRead => "config/read" {
params: v2::ConfigReadParams,
response: v2::ConfigReadResponse,
},
ConfigValueWrite => "config/value/write" {
params: v2::ConfigValueWriteParams,
response: v2::ConfigWriteResponse,
},
ConfigBatchWrite => "config/batchWrite" {
params: v2::ConfigBatchWriteParams,
response: v2::ConfigWriteResponse,
},
ConfigRequirementsRead => "configRequirements/read" {
params: #[ts(type = "undefined")] #[serde(skip_serializing_if = "Option::is_none")] Option<()>,
response: v2::ConfigRequirementsReadResponse,
},
GetAccount => "account/read" {
params: v2::GetAccountParams,
response: v2::GetAccountResponse,
},
/// DEPRECATED APIs below
NewConversation {
params: v1::NewConversationParams,
response: v1::NewConversationResponse,
},
GetConversationSummary {
params: v1::GetConversationSummaryParams,
response: v1::GetConversationSummaryResponse,
},
/// List recorded Codex conversations (rollouts) with optional pagination and search.
ListConversations {
params: v1::ListConversationsParams,
response: v1::ListConversationsResponse,
},
/// Resume a recorded Codex conversation from a rollout file.
ResumeConversation {
params: v1::ResumeConversationParams,
response: v1::ResumeConversationResponse,
},
/// Fork a recorded Codex conversation into a new session.
ForkConversation {
params: v1::ForkConversationParams,
response: v1::ForkConversationResponse,
},
ArchiveConversation {
params: v1::ArchiveConversationParams,
response: v1::ArchiveConversationResponse,
},
SendUserMessage {
params: v1::SendUserMessageParams,
response: v1::SendUserMessageResponse,
},
SendUserTurn {
params: v1::SendUserTurnParams,
response: v1::SendUserTurnResponse,
},
InterruptConversation {
params: v1::InterruptConversationParams,
response: v1::InterruptConversationResponse,
},
AddConversationListener {
params: v1::AddConversationListenerParams,
response: v1::AddConversationSubscriptionResponse,
},
RemoveConversationListener {
params: v1::RemoveConversationListenerParams,
response: v1::RemoveConversationSubscriptionResponse,
},
GitDiffToRemote {
params: v1::GitDiffToRemoteParams,
response: v1::GitDiffToRemoteResponse,
},
LoginApiKey {
params: v1::LoginApiKeyParams,
response: v1::LoginApiKeyResponse,
},
LoginChatGpt {
params: #[ts(type = "undefined")] #[serde(skip_serializing_if = "Option::is_none")] Option<()>,
response: v1::LoginChatGptResponse,
},
// DEPRECATED in favor of CancelLoginAccount
CancelLoginChatGpt {
params: v1::CancelLoginChatGptParams,
response: v1::CancelLoginChatGptResponse,
},
LogoutChatGpt {
params: #[ts(type = "undefined")] #[serde(skip_serializing_if = "Option::is_none")] Option<()>,
response: v1::LogoutChatGptResponse,
},
/// DEPRECATED in favor of GetAccount
GetAuthStatus {
params: v1::GetAuthStatusParams,
response: v1::GetAuthStatusResponse,
},
GetUserSavedConfig {
params: #[ts(type = "undefined")] #[serde(skip_serializing_if = "Option::is_none")] Option<()>,
response: v1::GetUserSavedConfigResponse,
},
SetDefaultModel {
params: v1::SetDefaultModelParams,
response: v1::SetDefaultModelResponse,
},
GetUserAgent {
params: #[ts(type = "undefined")] #[serde(skip_serializing_if = "Option::is_none")] Option<()>,
response: v1::GetUserAgentResponse,
},
UserInfo {
params: #[ts(type = "undefined")] #[serde(skip_serializing_if = "Option::is_none")] Option<()>,
response: v1::UserInfoResponse,
},
FuzzyFileSearch {
params: FuzzyFileSearchParams,
response: FuzzyFileSearchResponse,
},
/// Execute a command (argv vector) under the server's sandbox.
ExecOneOffCommand {
params: v1::ExecOneOffCommandParams,
response: v1::ExecOneOffCommandResponse,
},
}
/// Generates an `enum ServerRequest` where each variant is a request that the
/// server can send to the client along with the corresponding params and
/// response types. It also generates helper types used by the app/server
/// infrastructure (payload enum, request constructor, and export helpers).
macro_rules! server_request_definitions {
(
$(
$(#[$variant_meta:meta])*
$variant:ident $(=> $wire:literal)? {
params: $params:ty,
response: $response:ty,
}
),* $(,)?
) => {
/// Request initiated from the server and sent to the client.
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)]
#[serde(tag = "method", rename_all = "camelCase")]
pub enum ServerRequest {
$(
$(#[$variant_meta])*
$(#[serde(rename = $wire)] #[ts(rename = $wire)])?
$variant {
#[serde(rename = "id")]
request_id: RequestId,
params: $params,
},
)*
}
#[derive(Debug, Clone, PartialEq, JsonSchema)]
pub enum ServerRequestPayload {
$( $variant($params), )*
}
impl ServerRequestPayload {
pub fn request_with_id(self, request_id: RequestId) -> ServerRequest {
match self {
$(Self::$variant(params) => ServerRequest::$variant { request_id, params },)*
}
}
}
pub fn export_server_responses(
out_dir: &::std::path::Path,
) -> ::std::result::Result<(), ::ts_rs::ExportError> {
$(
<$response as ::ts_rs::TS>::export_all_to(out_dir)?;
)*
Ok(())
}
#[allow(clippy::vec_init_then_push)]
pub fn export_server_response_schemas(
out_dir: &Path,
) -> ::anyhow::Result<Vec<GeneratedSchema>> {
let mut schemas = Vec::new();
$(
schemas.push(crate::export::write_json_schema::<$response>(
out_dir,
concat!(stringify!($variant), "Response"),
)?);
)*
Ok(schemas)
}
#[allow(clippy::vec_init_then_push)]
pub fn export_server_param_schemas(
out_dir: &Path,
) -> ::anyhow::Result<Vec<GeneratedSchema>> {
let mut schemas = Vec::new();
$(
schemas.push(crate::export::write_json_schema::<$params>(
out_dir,
concat!(stringify!($variant), "Params"),
)?);
)*
Ok(schemas)
}
};
}
/// Generates `ServerNotification` enum and helpers, including a JSON Schema
/// exporter for each notification.
macro_rules! server_notification_definitions {
(
$(
$(#[$variant_meta:meta])*
$variant:ident $(=> $wire:literal)? ( $payload:ty )
),* $(,)?
) => {
/// Notification sent from the server to the client.
#[derive(Serialize, Deserialize, Debug, Clone, JsonSchema, TS, Display)]
#[serde(tag = "method", content = "params", rename_all = "camelCase")]
#[strum(serialize_all = "camelCase")]
pub enum ServerNotification {
$(
$(#[$variant_meta])*
$(#[serde(rename = $wire)] #[ts(rename = $wire)] #[strum(serialize = $wire)])?
$variant($payload),
)*
}
impl ServerNotification {
pub fn to_params(self) -> Result<serde_json::Value, serde_json::Error> {
match self {
$(Self::$variant(params) => serde_json::to_value(params),)*
}
}
}
impl TryFrom<JSONRPCNotification> for ServerNotification {
type Error = serde_json::Error;
fn try_from(value: JSONRPCNotification) -> Result<Self, serde_json::Error> {
serde_json::from_value(serde_json::to_value(value)?)
}
}
#[allow(clippy::vec_init_then_push)]
pub fn export_server_notification_schemas(
out_dir: &::std::path::Path,
) -> ::anyhow::Result<Vec<GeneratedSchema>> {
let mut schemas = Vec::new();
$(schemas.push(crate::export::write_json_schema::<$payload>(out_dir, stringify!($payload))?);)*
Ok(schemas)
}
};
}
/// Notifications sent from the client to the server.
macro_rules! client_notification_definitions {
(
$(
$(#[$variant_meta:meta])*
$variant:ident $( ( $payload:ty ) )?
),* $(,)?
) => {
#[derive(Serialize, Deserialize, Debug, Clone, JsonSchema, TS, Display)]
#[serde(tag = "method", content = "params", rename_all = "camelCase")]
#[strum(serialize_all = "camelCase")]
pub enum ClientNotification {
$(
$(#[$variant_meta])*
$variant $( ( $payload ) )?,
)*
}
pub fn export_client_notification_schemas(
_out_dir: &::std::path::Path,
) -> ::anyhow::Result<Vec<GeneratedSchema>> {
let schemas = Vec::new();
$( $(schemas.push(crate::export::write_json_schema::<$payload>(_out_dir, stringify!($payload))?);)? )*
Ok(schemas)
}
};
}
impl TryFrom<JSONRPCRequest> for ServerRequest {
type Error = serde_json::Error;
fn try_from(value: JSONRPCRequest) -> Result<Self, Self::Error> {
serde_json::from_value(serde_json::to_value(value)?)
}
}
server_request_definitions! {
/// NEW APIs
/// Sent when approval is requested for a specific command execution.
/// This request is used for Turns started via turn/start.
CommandExecutionRequestApproval => "item/commandExecution/requestApproval" {
params: v2::CommandExecutionRequestApprovalParams,
response: v2::CommandExecutionRequestApprovalResponse,
},
/// Sent when approval is requested for a specific file change.
/// This request is used for Turns started via turn/start.
FileChangeRequestApproval => "item/fileChange/requestApproval" {
params: v2::FileChangeRequestApprovalParams,
response: v2::FileChangeRequestApprovalResponse,
},
/// DEPRECATED APIs below
/// Request to approve a patch.
/// This request is used for Turns started via the legacy APIs (i.e. SendUserTurn, SendUserMessage).
ApplyPatchApproval {
params: v1::ApplyPatchApprovalParams,
response: v1::ApplyPatchApprovalResponse,
},
/// Request to exec a command.
/// This request is used for Turns started via the legacy APIs (i.e. SendUserTurn, SendUserMessage).
ExecCommandApproval {
params: v1::ExecCommandApprovalParams,
response: v1::ExecCommandApprovalResponse,
},
}
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)]
#[serde(rename_all = "camelCase")]
#[ts(rename_all = "camelCase")]
pub struct FuzzyFileSearchParams {
pub query: String,
pub roots: Vec<String>,
// if provided, will cancel any previous request that used the same value
pub cancellation_token: Option<String>,
}
/// Superset of [`codex_file_search::FileMatch`]
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)]
pub struct FuzzyFileSearchResult {
pub root: String,
pub path: String,
pub file_name: String,
pub score: u32,
pub indices: Option<Vec<u32>>,
}
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)]
pub struct FuzzyFileSearchResponse {
pub files: Vec<FuzzyFileSearchResult>,
}
server_notification_definitions! {
/// NEW NOTIFICATIONS
Error => "error" (v2::ErrorNotification),
ThreadStarted => "thread/started" (v2::ThreadStartedNotification),
ThreadTokenUsageUpdated => "thread/tokenUsage/updated" (v2::ThreadTokenUsageUpdatedNotification),
TurnStarted => "turn/started" (v2::TurnStartedNotification),
TurnCompleted => "turn/completed" (v2::TurnCompletedNotification),
TurnDiffUpdated => "turn/diff/updated" (v2::TurnDiffUpdatedNotification),
TurnPlanUpdated => "turn/plan/updated" (v2::TurnPlanUpdatedNotification),
ItemStarted => "item/started" (v2::ItemStartedNotification),
ItemCompleted => "item/completed" (v2::ItemCompletedNotification),
/// This event is internal-only. Used by Codex Cloud.
RawResponseItemCompleted => "rawResponseItem/completed" (v2::RawResponseItemCompletedNotification),
AgentMessageDelta => "item/agentMessage/delta" (v2::AgentMessageDeltaNotification),
CommandExecutionOutputDelta => "item/commandExecution/outputDelta" (v2::CommandExecutionOutputDeltaNotification),
TerminalInteraction => "item/commandExecution/terminalInteraction" (v2::TerminalInteractionNotification),
FileChangeOutputDelta => "item/fileChange/outputDelta" (v2::FileChangeOutputDeltaNotification),
McpToolCallProgress => "item/mcpToolCall/progress" (v2::McpToolCallProgressNotification),
McpServerOauthLoginCompleted => "mcpServer/oauthLogin/completed" (v2::McpServerOauthLoginCompletedNotification),
AccountUpdated => "account/updated" (v2::AccountUpdatedNotification),
AccountRateLimitsUpdated => "account/rateLimits/updated" (v2::AccountRateLimitsUpdatedNotification),
ReasoningSummaryTextDelta => "item/reasoning/summaryTextDelta" (v2::ReasoningSummaryTextDeltaNotification),
ReasoningSummaryPartAdded => "item/reasoning/summaryPartAdded" (v2::ReasoningSummaryPartAddedNotification),
ReasoningTextDelta => "item/reasoning/textDelta" (v2::ReasoningTextDeltaNotification),
ContextCompacted => "thread/compacted" (v2::ContextCompactedNotification),
DeprecationNotice => "deprecationNotice" (v2::DeprecationNoticeNotification),
/// Notifies the user of world-writable directories on Windows, which cannot be protected by the sandbox.
WindowsWorldWritableWarning => "windows/worldWritableWarning" (v2::WindowsWorldWritableWarningNotification),
#[serde(rename = "account/login/completed")]
#[ts(rename = "account/login/completed")]
#[strum(serialize = "account/login/completed")]
AccountLoginCompleted(v2::AccountLoginCompletedNotification),
/// DEPRECATED NOTIFICATIONS below
AuthStatusChange(v1::AuthStatusChangeNotification),
/// Deprecated: use `account/login/completed` instead.
LoginChatGptComplete(v1::LoginChatGptCompleteNotification),
SessionConfigured(v1::SessionConfiguredNotification),
}
client_notification_definitions! {
Initialized,
}
#[cfg(test)]
mod tests {
use super::*;
use anyhow::Result;
use codex_protocol::ThreadId;
use codex_protocol::account::PlanType;
use codex_protocol::parse_command::ParsedCommand;
use codex_protocol::protocol::AskForApproval;
use pretty_assertions::assert_eq;
use serde_json::json;
use std::path::PathBuf;
#[test]
fn serialize_new_conversation() -> Result<()> {
let request = ClientRequest::NewConversation {
request_id: RequestId::Integer(42),
params: v1::NewConversationParams {
model: Some("gpt-5.1-codex-max".to_string()),
model_provider: None,
profile: None,
cwd: None,
approval_policy: Some(AskForApproval::OnRequest),
sandbox: None,
config: None,
base_instructions: None,
developer_instructions: None,
compact_prompt: None,
include_apply_patch_tool: None,
},
};
assert_eq!(
json!({
"method": "newConversation",
"id": 42,
"params": {
"model": "gpt-5.1-codex-max",
"modelProvider": null,
"profile": null,
"cwd": null,
"approvalPolicy": "on-request",
"sandbox": null,
"config": null,
"baseInstructions": null,
"includeApplyPatchTool": null
}
}),
serde_json::to_value(&request)?,
);
Ok(())
}
#[test]
fn conversation_id_serializes_as_plain_string() -> Result<()> {
let id = ThreadId::from_string("67e55044-10b1-426f-9247-bb680e5fe0c8")?;
assert_eq!(
json!("67e55044-10b1-426f-9247-bb680e5fe0c8"),
serde_json::to_value(id)?
);
Ok(())
}
#[test]
fn conversation_id_deserializes_from_plain_string() -> Result<()> {
let id: ThreadId = serde_json::from_value(json!("67e55044-10b1-426f-9247-bb680e5fe0c8"))?;
assert_eq!(
ThreadId::from_string("67e55044-10b1-426f-9247-bb680e5fe0c8")?,
id,
);
Ok(())
}
#[test]
fn serialize_client_notification() -> Result<()> {
let notification = ClientNotification::Initialized;
// Note there is no "params" field for this notification.
assert_eq!(
json!({
"method": "initialized",
}),
serde_json::to_value(&notification)?,
);
Ok(())
}
#[test]
fn serialize_server_request() -> Result<()> {
let conversation_id = ThreadId::from_string("67e55044-10b1-426f-9247-bb680e5fe0c8")?;
let params = v1::ExecCommandApprovalParams {
conversation_id,
call_id: "call-42".to_string(),
command: vec!["echo".to_string(), "hello".to_string()],
cwd: PathBuf::from("/tmp"),
reason: Some("because tests".to_string()),
parsed_cmd: vec![ParsedCommand::Unknown {
cmd: "echo hello".to_string(),
}],
};
let request = ServerRequest::ExecCommandApproval {
request_id: RequestId::Integer(7),
params: params.clone(),
};
assert_eq!(
json!({
"method": "execCommandApproval",
"id": 7,
"params": {
"conversationId": "67e55044-10b1-426f-9247-bb680e5fe0c8",
"callId": "call-42",
"command": ["echo", "hello"],
"cwd": "/tmp",
"reason": "because tests",
"parsedCmd": [
{
"type": "unknown",
"cmd": "echo hello"
}
]
}
}),
serde_json::to_value(&request)?,
);
let payload = ServerRequestPayload::ExecCommandApproval(params);
assert_eq!(payload.request_with_id(RequestId::Integer(7)), request);
Ok(())
}
#[test]
fn serialize_get_account_rate_limits() -> Result<()> {
let request = ClientRequest::GetAccountRateLimits {
request_id: RequestId::Integer(1),
params: None,
};
assert_eq!(
json!({
"method": "account/rateLimits/read",
"id": 1,
}),
serde_json::to_value(&request)?,
);
Ok(())
}
#[test]
fn serialize_config_requirements_read() -> Result<()> {
let request = ClientRequest::ConfigRequirementsRead {
request_id: RequestId::Integer(1),
params: None,
};
assert_eq!(
json!({
"method": "configRequirements/read",
"id": 1,
}),
serde_json::to_value(&request)?,
);
Ok(())
}
#[test]
fn serialize_account_login_api_key() -> Result<()> {
let request = ClientRequest::LoginAccount {
request_id: RequestId::Integer(2),
params: v2::LoginAccountParams::ApiKey {
api_key: "secret".to_string(),
},
};
assert_eq!(
json!({
"method": "account/login/start",
"id": 2,
"params": {
"type": "apiKey",
"apiKey": "secret"
}
}),
serde_json::to_value(&request)?,
);
Ok(())
}
#[test]
fn serialize_account_login_chatgpt() -> Result<()> {
let request = ClientRequest::LoginAccount {
request_id: RequestId::Integer(3),
params: v2::LoginAccountParams::Chatgpt,
};
assert_eq!(
json!({
"method": "account/login/start",
"id": 3,
"params": {
"type": "chatgpt"
}
}),
serde_json::to_value(&request)?,
);
Ok(())
}
#[test]
fn serialize_account_logout() -> Result<()> {
let request = ClientRequest::LogoutAccount {
request_id: RequestId::Integer(4),
params: None,
};
assert_eq!(
json!({
"method": "account/logout",
"id": 4,
}),
serde_json::to_value(&request)?,
);
Ok(())
}
#[test]
fn serialize_get_account() -> Result<()> {
let request = ClientRequest::GetAccount {
request_id: RequestId::Integer(5),
params: v2::GetAccountParams {
refresh_token: false,
},
};
assert_eq!(
json!({
"method": "account/read",
"id": 5,
"params": {
"refreshToken": false
}
}),
serde_json::to_value(&request)?,
);
Ok(())
}
#[test]
fn account_serializes_fields_in_camel_case() -> Result<()> {
let api_key = v2::Account::ApiKey {};
assert_eq!(
json!({
"type": "apiKey",
}),
serde_json::to_value(&api_key)?,
);
let chatgpt = v2::Account::Chatgpt {
email: "user@example.com".to_string(),
plan_type: PlanType::Plus,
};
assert_eq!(
json!({
"type": "chatgpt",
"email": "user@example.com",
"planType": "plus",
}),
serde_json::to_value(&chatgpt)?,
);
Ok(())
}
#[test]
fn serialize_list_models() -> Result<()> {
let request = ClientRequest::ModelList {
request_id: RequestId::Integer(6),
params: v2::ModelListParams::default(),
};
assert_eq!(
json!({
"method": "model/list",
"id": 6,
"params": {
"limit": null,
"cursor": null
}
}),
serde_json::to_value(&request)?,
);
Ok(())
}
}

View File

@@ -0,0 +1,15 @@
use crate::protocol::v1;
use crate::protocol::v2;
impl From<v1::ExecOneOffCommandParams> for v2::CommandExecParams {
fn from(value: v1::ExecOneOffCommandParams) -> Self {
Self {
command: value.command,
timeout_ms: value
.timeout_ms
.map(|timeout| i64::try_from(timeout).unwrap_or(60_000)),
cwd: value.cwd,
sandbox_policy: value.sandbox_policy.map(std::convert::Into::into),
}
}
}

View File

@@ -0,0 +1,8 @@
// Module declarations for the app-server protocol namespace.
// Exposes protocol pieces used by `lib.rs` via `pub use protocol::common::*;`.
pub mod common;
mod mappers;
pub mod thread_history;
pub mod v1;
pub mod v2;

View File

@@ -0,0 +1,524 @@
use crate::protocol::v2::ThreadItem;
use crate::protocol::v2::Turn;
use crate::protocol::v2::TurnError;
use crate::protocol::v2::TurnStatus;
use crate::protocol::v2::UserInput;
use codex_protocol::protocol::AgentReasoningEvent;
use codex_protocol::protocol::AgentReasoningRawContentEvent;
use codex_protocol::protocol::EventMsg;
use codex_protocol::protocol::ThreadRolledBackEvent;
use codex_protocol::protocol::TurnAbortedEvent;
use codex_protocol::protocol::UserMessageEvent;
/// Convert persisted [`EventMsg`] entries into a sequence of [`Turn`] values.
///
/// The purpose of this is to convert the EventMsgs persisted in a rollout file
/// into a sequence of Turns and ThreadItems, which allows the client to render
/// the historical messages when resuming a thread.
pub fn build_turns_from_event_msgs(events: &[EventMsg]) -> Vec<Turn> {
let mut builder = ThreadHistoryBuilder::new();
for event in events {
builder.handle_event(event);
}
builder.finish()
}
struct ThreadHistoryBuilder {
turns: Vec<Turn>,
current_turn: Option<PendingTurn>,
next_turn_index: i64,
next_item_index: i64,
}
impl ThreadHistoryBuilder {
fn new() -> Self {
Self {
turns: Vec::new(),
current_turn: None,
next_turn_index: 1,
next_item_index: 1,
}
}
fn finish(mut self) -> Vec<Turn> {
self.finish_current_turn();
self.turns
}
/// This function should handle all EventMsg variants that can be persisted in a rollout file.
/// See `should_persist_event_msg` in `codex-rs/core/rollout/policy.rs`.
fn handle_event(&mut self, event: &EventMsg) {
match event {
EventMsg::UserMessage(payload) => self.handle_user_message(payload),
EventMsg::AgentMessage(payload) => self.handle_agent_message(payload.message.clone()),
EventMsg::AgentReasoning(payload) => self.handle_agent_reasoning(payload),
EventMsg::AgentReasoningRawContent(payload) => {
self.handle_agent_reasoning_raw_content(payload)
}
EventMsg::TokenCount(_) => {}
EventMsg::EnteredReviewMode(_) => {}
EventMsg::ExitedReviewMode(_) => {}
EventMsg::ThreadRolledBack(payload) => self.handle_thread_rollback(payload),
EventMsg::UndoCompleted(_) => {}
EventMsg::TurnAborted(payload) => self.handle_turn_aborted(payload),
_ => {}
}
}
fn handle_user_message(&mut self, payload: &UserMessageEvent) {
self.finish_current_turn();
let mut turn = self.new_turn();
let id = self.next_item_id();
let content = self.build_user_inputs(payload);
turn.items.push(ThreadItem::UserMessage { id, content });
self.current_turn = Some(turn);
}
fn handle_agent_message(&mut self, text: String) {
if text.is_empty() {
return;
}
let id = self.next_item_id();
self.ensure_turn()
.items
.push(ThreadItem::AgentMessage { id, text });
}
fn handle_agent_reasoning(&mut self, payload: &AgentReasoningEvent) {
if payload.text.is_empty() {
return;
}
// If the last item is a reasoning item, add the new text to the summary.
if let Some(ThreadItem::Reasoning { summary, .. }) = self.ensure_turn().items.last_mut() {
summary.push(payload.text.clone());
return;
}
// Otherwise, create a new reasoning item.
let id = self.next_item_id();
self.ensure_turn().items.push(ThreadItem::Reasoning {
id,
summary: vec![payload.text.clone()],
content: Vec::new(),
});
}
fn handle_agent_reasoning_raw_content(&mut self, payload: &AgentReasoningRawContentEvent) {
if payload.text.is_empty() {
return;
}
// If the last item is a reasoning item, add the new text to the content.
if let Some(ThreadItem::Reasoning { content, .. }) = self.ensure_turn().items.last_mut() {
content.push(payload.text.clone());
return;
}
// Otherwise, create a new reasoning item.
let id = self.next_item_id();
self.ensure_turn().items.push(ThreadItem::Reasoning {
id,
summary: Vec::new(),
content: vec![payload.text.clone()],
});
}
fn handle_turn_aborted(&mut self, _payload: &TurnAbortedEvent) {
let Some(turn) = self.current_turn.as_mut() else {
return;
};
turn.status = TurnStatus::Interrupted;
}
fn handle_thread_rollback(&mut self, payload: &ThreadRolledBackEvent) {
self.finish_current_turn();
let n = usize::try_from(payload.num_turns).unwrap_or(usize::MAX);
if n >= self.turns.len() {
self.turns.clear();
} else {
self.turns.truncate(self.turns.len().saturating_sub(n));
}
// Re-number subsequent synthetic ids so the pruned history is consistent.
self.next_turn_index =
i64::try_from(self.turns.len().saturating_add(1)).unwrap_or(i64::MAX);
let item_count: usize = self.turns.iter().map(|t| t.items.len()).sum();
self.next_item_index = i64::try_from(item_count.saturating_add(1)).unwrap_or(i64::MAX);
}
fn finish_current_turn(&mut self) {
if let Some(turn) = self.current_turn.take() {
if turn.items.is_empty() {
return;
}
self.turns.push(turn.into());
}
}
fn new_turn(&mut self) -> PendingTurn {
PendingTurn {
id: self.next_turn_id(),
items: Vec::new(),
error: None,
status: TurnStatus::Completed,
}
}
fn ensure_turn(&mut self) -> &mut PendingTurn {
if self.current_turn.is_none() {
let turn = self.new_turn();
return self.current_turn.insert(turn);
}
if let Some(turn) = self.current_turn.as_mut() {
return turn;
}
unreachable!("current turn must exist after initialization");
}
fn next_turn_id(&mut self) -> String {
let id = format!("turn-{}", self.next_turn_index);
self.next_turn_index += 1;
id
}
fn next_item_id(&mut self) -> String {
let id = format!("item-{}", self.next_item_index);
self.next_item_index += 1;
id
}
fn build_user_inputs(&self, payload: &UserMessageEvent) -> Vec<UserInput> {
let mut content = Vec::new();
if !payload.message.trim().is_empty() {
content.push(UserInput::Text {
text: payload.message.clone(),
});
}
if let Some(images) = &payload.images {
for image in images {
content.push(UserInput::Image { url: image.clone() });
}
}
content
}
}
struct PendingTurn {
id: String,
items: Vec<ThreadItem>,
error: Option<TurnError>,
status: TurnStatus,
}
impl From<PendingTurn> for Turn {
fn from(value: PendingTurn) -> Self {
Self {
id: value.id,
items: value.items,
error: value.error,
status: value.status,
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use codex_protocol::protocol::AgentMessageEvent;
use codex_protocol::protocol::AgentReasoningEvent;
use codex_protocol::protocol::AgentReasoningRawContentEvent;
use codex_protocol::protocol::ThreadRolledBackEvent;
use codex_protocol::protocol::TurnAbortReason;
use codex_protocol::protocol::TurnAbortedEvent;
use codex_protocol::protocol::UserMessageEvent;
use pretty_assertions::assert_eq;
#[test]
fn builds_multiple_turns_with_reasoning_items() {
let events = vec![
EventMsg::UserMessage(UserMessageEvent {
message: "First turn".into(),
images: Some(vec!["https://example.com/one.png".into()]),
}),
EventMsg::AgentMessage(AgentMessageEvent {
message: "Hi there".into(),
}),
EventMsg::AgentReasoning(AgentReasoningEvent {
text: "thinking".into(),
}),
EventMsg::AgentReasoningRawContent(AgentReasoningRawContentEvent {
text: "full reasoning".into(),
}),
EventMsg::UserMessage(UserMessageEvent {
message: "Second turn".into(),
images: None,
}),
EventMsg::AgentMessage(AgentMessageEvent {
message: "Reply two".into(),
}),
];
let turns = build_turns_from_event_msgs(&events);
assert_eq!(turns.len(), 2);
let first = &turns[0];
assert_eq!(first.id, "turn-1");
assert_eq!(first.status, TurnStatus::Completed);
assert_eq!(first.items.len(), 3);
assert_eq!(
first.items[0],
ThreadItem::UserMessage {
id: "item-1".into(),
content: vec![
UserInput::Text {
text: "First turn".into(),
},
UserInput::Image {
url: "https://example.com/one.png".into(),
}
],
}
);
assert_eq!(
first.items[1],
ThreadItem::AgentMessage {
id: "item-2".into(),
text: "Hi there".into(),
}
);
assert_eq!(
first.items[2],
ThreadItem::Reasoning {
id: "item-3".into(),
summary: vec!["thinking".into()],
content: vec!["full reasoning".into()],
}
);
let second = &turns[1];
assert_eq!(second.id, "turn-2");
assert_eq!(second.items.len(), 2);
assert_eq!(
second.items[0],
ThreadItem::UserMessage {
id: "item-4".into(),
content: vec![UserInput::Text {
text: "Second turn".into()
}],
}
);
assert_eq!(
second.items[1],
ThreadItem::AgentMessage {
id: "item-5".into(),
text: "Reply two".into(),
}
);
}
#[test]
fn splits_reasoning_when_interleaved() {
let events = vec![
EventMsg::UserMessage(UserMessageEvent {
message: "Turn start".into(),
images: None,
}),
EventMsg::AgentReasoning(AgentReasoningEvent {
text: "first summary".into(),
}),
EventMsg::AgentReasoningRawContent(AgentReasoningRawContentEvent {
text: "first content".into(),
}),
EventMsg::AgentMessage(AgentMessageEvent {
message: "interlude".into(),
}),
EventMsg::AgentReasoning(AgentReasoningEvent {
text: "second summary".into(),
}),
];
let turns = build_turns_from_event_msgs(&events);
assert_eq!(turns.len(), 1);
let turn = &turns[0];
assert_eq!(turn.items.len(), 4);
assert_eq!(
turn.items[1],
ThreadItem::Reasoning {
id: "item-2".into(),
summary: vec!["first summary".into()],
content: vec!["first content".into()],
}
);
assert_eq!(
turn.items[3],
ThreadItem::Reasoning {
id: "item-4".into(),
summary: vec!["second summary".into()],
content: Vec::new(),
}
);
}
#[test]
fn marks_turn_as_interrupted_when_aborted() {
let events = vec![
EventMsg::UserMessage(UserMessageEvent {
message: "Please do the thing".into(),
images: None,
}),
EventMsg::AgentMessage(AgentMessageEvent {
message: "Working...".into(),
}),
EventMsg::TurnAborted(TurnAbortedEvent {
reason: TurnAbortReason::Replaced,
}),
EventMsg::UserMessage(UserMessageEvent {
message: "Let's try again".into(),
images: None,
}),
EventMsg::AgentMessage(AgentMessageEvent {
message: "Second attempt complete.".into(),
}),
];
let turns = build_turns_from_event_msgs(&events);
assert_eq!(turns.len(), 2);
let first_turn = &turns[0];
assert_eq!(first_turn.status, TurnStatus::Interrupted);
assert_eq!(first_turn.items.len(), 2);
assert_eq!(
first_turn.items[0],
ThreadItem::UserMessage {
id: "item-1".into(),
content: vec![UserInput::Text {
text: "Please do the thing".into()
}],
}
);
assert_eq!(
first_turn.items[1],
ThreadItem::AgentMessage {
id: "item-2".into(),
text: "Working...".into(),
}
);
let second_turn = &turns[1];
assert_eq!(second_turn.status, TurnStatus::Completed);
assert_eq!(second_turn.items.len(), 2);
assert_eq!(
second_turn.items[0],
ThreadItem::UserMessage {
id: "item-3".into(),
content: vec![UserInput::Text {
text: "Let's try again".into()
}],
}
);
assert_eq!(
second_turn.items[1],
ThreadItem::AgentMessage {
id: "item-4".into(),
text: "Second attempt complete.".into(),
}
);
}
#[test]
fn drops_last_turns_on_thread_rollback() {
let events = vec![
EventMsg::UserMessage(UserMessageEvent {
message: "First".into(),
images: None,
}),
EventMsg::AgentMessage(AgentMessageEvent {
message: "A1".into(),
}),
EventMsg::UserMessage(UserMessageEvent {
message: "Second".into(),
images: None,
}),
EventMsg::AgentMessage(AgentMessageEvent {
message: "A2".into(),
}),
EventMsg::ThreadRolledBack(ThreadRolledBackEvent { num_turns: 1 }),
EventMsg::UserMessage(UserMessageEvent {
message: "Third".into(),
images: None,
}),
EventMsg::AgentMessage(AgentMessageEvent {
message: "A3".into(),
}),
];
let turns = build_turns_from_event_msgs(&events);
let expected = vec![
Turn {
id: "turn-1".into(),
status: TurnStatus::Completed,
error: None,
items: vec![
ThreadItem::UserMessage {
id: "item-1".into(),
content: vec![UserInput::Text {
text: "First".into(),
}],
},
ThreadItem::AgentMessage {
id: "item-2".into(),
text: "A1".into(),
},
],
},
Turn {
id: "turn-2".into(),
status: TurnStatus::Completed,
error: None,
items: vec![
ThreadItem::UserMessage {
id: "item-3".into(),
content: vec![UserInput::Text {
text: "Third".into(),
}],
},
ThreadItem::AgentMessage {
id: "item-4".into(),
text: "A3".into(),
},
],
},
];
assert_eq!(turns, expected);
}
#[test]
fn thread_rollback_clears_all_turns_when_num_turns_exceeds_history() {
let events = vec![
EventMsg::UserMessage(UserMessageEvent {
message: "One".into(),
images: None,
}),
EventMsg::AgentMessage(AgentMessageEvent {
message: "A1".into(),
}),
EventMsg::UserMessage(UserMessageEvent {
message: "Two".into(),
images: None,
}),
EventMsg::AgentMessage(AgentMessageEvent {
message: "A2".into(),
}),
EventMsg::ThreadRolledBack(ThreadRolledBackEvent { num_turns: 99 }),
];
let turns = build_turns_from_event_msgs(&events);
assert_eq!(turns, Vec::<Turn>::new());
}
}

View File

@@ -0,0 +1,480 @@
use std::collections::HashMap;
use std::path::PathBuf;
use codex_protocol::ThreadId;
use codex_protocol::config_types::ForcedLoginMethod;
use codex_protocol::config_types::ReasoningSummary;
use codex_protocol::config_types::SandboxMode;
use codex_protocol::config_types::Verbosity;
use codex_protocol::models::ResponseItem;
use codex_protocol::openai_models::ReasoningEffort;
use codex_protocol::parse_command::ParsedCommand;
use codex_protocol::protocol::AskForApproval;
use codex_protocol::protocol::EventMsg;
use codex_protocol::protocol::FileChange;
use codex_protocol::protocol::ReviewDecision;
use codex_protocol::protocol::SandboxPolicy;
use codex_protocol::protocol::SessionSource;
use codex_protocol::protocol::TurnAbortReason;
use codex_utils_absolute_path::AbsolutePathBuf;
use schemars::JsonSchema;
use serde::Deserialize;
use serde::Serialize;
use ts_rs::TS;
use uuid::Uuid;
// Reuse shared types defined in `common.rs`.
use crate::protocol::common::AuthMode;
use crate::protocol::common::GitSha;
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Default, JsonSchema, TS)]
#[serde(rename_all = "camelCase")]
pub struct InitializeParams {
pub client_info: ClientInfo,
}
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Default, JsonSchema, TS)]
#[serde(rename_all = "camelCase")]
pub struct ClientInfo {
pub name: String,
pub title: Option<String>,
pub version: String,
}
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)]
#[serde(rename_all = "camelCase")]
pub struct InitializeResponse {
pub user_agent: String,
}
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Default, JsonSchema, TS)]
#[serde(rename_all = "camelCase")]
pub struct NewConversationParams {
pub model: Option<String>,
pub model_provider: Option<String>,
pub profile: Option<String>,
pub cwd: Option<String>,
pub approval_policy: Option<AskForApproval>,
pub sandbox: Option<SandboxMode>,
pub config: Option<HashMap<String, serde_json::Value>>,
pub base_instructions: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub developer_instructions: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub compact_prompt: Option<String>,
pub include_apply_patch_tool: Option<bool>,
}
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)]
#[serde(rename_all = "camelCase")]
pub struct NewConversationResponse {
pub conversation_id: ThreadId,
pub model: String,
pub reasoning_effort: Option<ReasoningEffort>,
pub rollout_path: PathBuf,
}
#[derive(Serialize, Deserialize, Debug, Clone, JsonSchema, TS)]
#[serde(rename_all = "camelCase")]
pub struct ResumeConversationResponse {
pub conversation_id: ThreadId,
pub model: String,
pub initial_messages: Option<Vec<EventMsg>>,
pub rollout_path: PathBuf,
}
#[derive(Serialize, Deserialize, Debug, Clone, JsonSchema, TS)]
#[serde(rename_all = "camelCase")]
pub struct ForkConversationResponse {
pub conversation_id: ThreadId,
pub model: String,
pub initial_messages: Option<Vec<EventMsg>>,
pub rollout_path: PathBuf,
}
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)]
#[serde(untagged)]
pub enum GetConversationSummaryParams {
RolloutPath {
#[serde(rename = "rolloutPath")]
rollout_path: PathBuf,
},
ThreadId {
#[serde(rename = "conversationId")]
conversation_id: ThreadId,
},
}
#[derive(Serialize, Deserialize, Debug, Clone, JsonSchema, TS)]
#[serde(rename_all = "camelCase")]
pub struct GetConversationSummaryResponse {
pub summary: ConversationSummary,
}
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Default, JsonSchema, TS)]
#[serde(rename_all = "camelCase")]
pub struct ListConversationsParams {
pub page_size: Option<usize>,
pub cursor: Option<String>,
pub model_providers: Option<Vec<String>>,
}
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)]
#[serde(rename_all = "camelCase")]
pub struct ConversationSummary {
pub conversation_id: ThreadId,
pub path: PathBuf,
pub preview: String,
pub timestamp: Option<String>,
pub model_provider: String,
pub cwd: PathBuf,
pub cli_version: String,
pub source: SessionSource,
pub git_info: Option<ConversationGitInfo>,
}
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)]
#[serde(rename_all = "snake_case")]
pub struct ConversationGitInfo {
pub sha: Option<String>,
pub branch: Option<String>,
pub origin_url: Option<String>,
}
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)]
#[serde(rename_all = "camelCase")]
pub struct ListConversationsResponse {
pub items: Vec<ConversationSummary>,
pub next_cursor: Option<String>,
}
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)]
#[serde(rename_all = "camelCase")]
pub struct ResumeConversationParams {
pub path: Option<PathBuf>,
pub conversation_id: Option<ThreadId>,
pub history: Option<Vec<ResponseItem>>,
pub overrides: Option<NewConversationParams>,
}
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)]
#[serde(rename_all = "camelCase")]
pub struct ForkConversationParams {
pub path: Option<PathBuf>,
pub conversation_id: Option<ThreadId>,
pub overrides: Option<NewConversationParams>,
}
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)]
#[serde(rename_all = "camelCase")]
pub struct AddConversationSubscriptionResponse {
#[schemars(with = "String")]
pub subscription_id: Uuid,
}
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)]
#[serde(rename_all = "camelCase")]
pub struct ArchiveConversationParams {
pub conversation_id: ThreadId,
pub rollout_path: PathBuf,
}
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)]
#[serde(rename_all = "camelCase")]
pub struct ArchiveConversationResponse {}
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)]
#[serde(rename_all = "camelCase")]
pub struct RemoveConversationSubscriptionResponse {}
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)]
#[serde(rename_all = "camelCase")]
pub struct LoginApiKeyParams {
pub api_key: String,
}
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)]
#[serde(rename_all = "camelCase")]
pub struct LoginApiKeyResponse {}
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)]
#[serde(rename_all = "camelCase")]
pub struct LoginChatGptResponse {
#[schemars(with = "String")]
pub login_id: Uuid,
pub auth_url: String,
}
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)]
#[serde(rename_all = "camelCase")]
pub struct GitDiffToRemoteResponse {
pub sha: GitSha,
pub diff: String,
}
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)]
#[serde(rename_all = "camelCase")]
pub struct ApplyPatchApprovalParams {
pub conversation_id: ThreadId,
/// Use to correlate this with [codex_core::protocol::PatchApplyBeginEvent]
/// and [codex_core::protocol::PatchApplyEndEvent].
pub call_id: String,
pub file_changes: HashMap<PathBuf, FileChange>,
/// Optional explanatory reason (e.g. request for extra write access).
pub reason: Option<String>,
/// When set, the agent is asking the user to allow writes under this root
/// for the remainder of the session (unclear if this is honored today).
pub grant_root: Option<PathBuf>,
}
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)]
#[serde(rename_all = "camelCase")]
pub struct ApplyPatchApprovalResponse {
pub decision: ReviewDecision,
}
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)]
#[serde(rename_all = "camelCase")]
pub struct ExecCommandApprovalParams {
pub conversation_id: ThreadId,
/// Use to correlate this with [codex_core::protocol::ExecCommandBeginEvent]
/// and [codex_core::protocol::ExecCommandEndEvent].
pub call_id: String,
pub command: Vec<String>,
pub cwd: PathBuf,
pub reason: Option<String>,
pub parsed_cmd: Vec<ParsedCommand>,
}
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)]
pub struct ExecCommandApprovalResponse {
pub decision: ReviewDecision,
}
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)]
#[serde(rename_all = "camelCase")]
pub struct CancelLoginChatGptParams {
#[schemars(with = "String")]
pub login_id: Uuid,
}
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)]
#[serde(rename_all = "camelCase")]
pub struct GitDiffToRemoteParams {
pub cwd: PathBuf,
}
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)]
#[serde(rename_all = "camelCase")]
pub struct CancelLoginChatGptResponse {}
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)]
#[serde(rename_all = "camelCase")]
pub struct LogoutChatGptParams {}
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)]
#[serde(rename_all = "camelCase")]
pub struct LogoutChatGptResponse {}
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)]
#[serde(rename_all = "camelCase")]
pub struct GetAuthStatusParams {
pub include_token: Option<bool>,
pub refresh_token: Option<bool>,
}
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)]
#[serde(rename_all = "camelCase")]
pub struct ExecOneOffCommandParams {
pub command: Vec<String>,
pub timeout_ms: Option<u64>,
pub cwd: Option<PathBuf>,
pub sandbox_policy: Option<SandboxPolicy>,
}
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)]
#[serde(rename_all = "camelCase")]
pub struct ExecOneOffCommandResponse {
pub exit_code: i32,
pub stdout: String,
pub stderr: String,
}
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)]
#[serde(rename_all = "camelCase")]
pub struct GetAuthStatusResponse {
pub auth_method: Option<AuthMode>,
pub auth_token: Option<String>,
pub requires_openai_auth: Option<bool>,
}
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)]
#[serde(rename_all = "camelCase")]
pub struct GetUserAgentResponse {
pub user_agent: String,
}
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)]
#[serde(rename_all = "camelCase")]
pub struct UserInfoResponse {
pub alleged_user_email: Option<String>,
}
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)]
#[serde(rename_all = "camelCase")]
pub struct GetUserSavedConfigResponse {
pub config: UserSavedConfig,
}
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)]
#[serde(rename_all = "camelCase")]
pub struct SetDefaultModelParams {
pub model: Option<String>,
pub reasoning_effort: Option<ReasoningEffort>,
}
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)]
#[serde(rename_all = "camelCase")]
pub struct SetDefaultModelResponse {}
#[derive(Deserialize, Debug, Clone, PartialEq, Serialize, JsonSchema, TS)]
#[serde(rename_all = "camelCase")]
pub struct UserSavedConfig {
pub approval_policy: Option<AskForApproval>,
pub sandbox_mode: Option<SandboxMode>,
pub sandbox_settings: Option<SandboxSettings>,
pub forced_chatgpt_workspace_id: Option<String>,
pub forced_login_method: Option<ForcedLoginMethod>,
pub model: Option<String>,
pub model_reasoning_effort: Option<ReasoningEffort>,
pub model_reasoning_summary: Option<ReasoningSummary>,
pub model_verbosity: Option<Verbosity>,
pub tools: Option<Tools>,
pub profile: Option<String>,
pub profiles: HashMap<String, Profile>,
}
#[derive(Deserialize, Debug, Clone, PartialEq, Serialize, JsonSchema, TS)]
#[serde(rename_all = "camelCase")]
pub struct Profile {
pub model: Option<String>,
pub model_provider: Option<String>,
pub approval_policy: Option<AskForApproval>,
pub model_reasoning_effort: Option<ReasoningEffort>,
pub model_reasoning_summary: Option<ReasoningSummary>,
pub model_verbosity: Option<Verbosity>,
pub chatgpt_base_url: Option<String>,
}
#[derive(Deserialize, Debug, Clone, PartialEq, Serialize, JsonSchema, TS)]
#[serde(rename_all = "camelCase")]
pub struct Tools {
pub web_search: Option<bool>,
pub view_image: Option<bool>,
}
#[derive(Deserialize, Debug, Clone, PartialEq, Serialize, JsonSchema, TS)]
#[serde(rename_all = "camelCase")]
pub struct SandboxSettings {
#[serde(default)]
pub writable_roots: Vec<AbsolutePathBuf>,
pub network_access: Option<bool>,
pub exclude_tmpdir_env_var: Option<bool>,
pub exclude_slash_tmp: Option<bool>,
}
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)]
#[serde(rename_all = "camelCase")]
pub struct SendUserMessageParams {
pub conversation_id: ThreadId,
pub items: Vec<InputItem>,
}
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)]
#[serde(rename_all = "camelCase")]
pub struct SendUserTurnParams {
pub conversation_id: ThreadId,
pub items: Vec<InputItem>,
pub cwd: PathBuf,
pub approval_policy: AskForApproval,
pub sandbox_policy: SandboxPolicy,
pub model: String,
pub effort: Option<ReasoningEffort>,
pub summary: ReasoningSummary,
/// Optional JSON Schema used to constrain the final assistant message for this turn.
pub output_schema: Option<serde_json::Value>,
}
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)]
#[serde(rename_all = "camelCase")]
pub struct SendUserTurnResponse {}
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)]
#[serde(rename_all = "camelCase")]
pub struct InterruptConversationParams {
pub conversation_id: ThreadId,
}
#[derive(Serialize, Deserialize, Debug, Clone, JsonSchema, TS)]
#[serde(rename_all = "camelCase")]
pub struct InterruptConversationResponse {
pub abort_reason: TurnAbortReason,
}
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)]
#[serde(rename_all = "camelCase")]
pub struct SendUserMessageResponse {}
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)]
#[serde(rename_all = "camelCase")]
pub struct AddConversationListenerParams {
pub conversation_id: ThreadId,
#[serde(default)]
pub experimental_raw_events: bool,
}
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)]
#[serde(rename_all = "camelCase")]
pub struct RemoveConversationListenerParams {
#[schemars(with = "String")]
pub subscription_id: Uuid,
}
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)]
#[serde(rename_all = "camelCase")]
#[serde(tag = "type", content = "data")]
pub enum InputItem {
Text { text: String },
Image { image_url: String },
LocalImage { path: PathBuf },
}
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)]
#[serde(rename_all = "camelCase")]
/// Deprecated in favor of AccountLoginCompletedNotification.
pub struct LoginChatGptCompleteNotification {
#[schemars(with = "String")]
pub login_id: Uuid,
pub success: bool,
pub error: Option<String>,
}
#[derive(Serialize, Deserialize, Debug, Clone, JsonSchema, TS)]
#[serde(rename_all = "camelCase")]
pub struct SessionConfiguredNotification {
pub session_id: ThreadId,
pub model: String,
pub reasoning_effort: Option<ReasoningEffort>,
pub history_log_id: u64,
#[ts(type = "number")]
pub history_entry_count: usize,
pub initial_messages: Option<Vec<EventMsg>>,
pub rollout_path: PathBuf,
}
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)]
#[serde(rename_all = "camelCase")]
/// Deprecated notification. Use AccountUpdatedNotification instead.
pub struct AuthStatusChangeNotification {
pub auth_method: Option<AuthMode>,
}

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,6 @@
load("//:defs.bzl", "codex_rust_crate")
codex_rust_crate(
name = "codex-app-server-test-client",
crate_name = "codex_app_server_test_client",
)

1298
codex-rs/app-server-test-client/Cargo.lock generated Normal file

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,17 @@
[package]
name = "codex-app-server-test-client"
version.workspace = true
edition.workspace = true
license.workspace = true
[lints]
workspace = true
[dependencies]
anyhow = { workspace = true }
clap = { workspace = true, features = ["derive", "env"] }
codex-app-server-protocol = { workspace = true }
codex-protocol = { workspace = true }
serde = { workspace = true, features = ["derive"] }
serde_json = { workspace = true }
uuid = { workspace = true, features = ["v4"] }

View File

@@ -0,0 +1,2 @@
# App Server Test Client
Exercises simple `codex app-server` flows end-to-end, logging JSON-RPC messages sent between client and server to stdout.

View File

@@ -0,0 +1,940 @@
use std::collections::VecDeque;
use std::io::BufRead;
use std::io::BufReader;
use std::io::Write;
use std::process::Child;
use std::process::ChildStdin;
use std::process::ChildStdout;
use std::process::Command;
use std::process::Stdio;
use std::thread;
use std::time::Duration;
use anyhow::Context;
use anyhow::Result;
use anyhow::bail;
use clap::ArgAction;
use clap::Parser;
use clap::Subcommand;
use codex_app_server_protocol::AddConversationListenerParams;
use codex_app_server_protocol::AddConversationSubscriptionResponse;
use codex_app_server_protocol::AskForApproval;
use codex_app_server_protocol::ClientInfo;
use codex_app_server_protocol::ClientRequest;
use codex_app_server_protocol::CommandExecutionApprovalDecision;
use codex_app_server_protocol::CommandExecutionRequestApprovalParams;
use codex_app_server_protocol::CommandExecutionRequestApprovalResponse;
use codex_app_server_protocol::FileChangeApprovalDecision;
use codex_app_server_protocol::FileChangeRequestApprovalParams;
use codex_app_server_protocol::FileChangeRequestApprovalResponse;
use codex_app_server_protocol::GetAccountRateLimitsResponse;
use codex_app_server_protocol::InitializeParams;
use codex_app_server_protocol::InitializeResponse;
use codex_app_server_protocol::InputItem;
use codex_app_server_protocol::JSONRPCMessage;
use codex_app_server_protocol::JSONRPCNotification;
use codex_app_server_protocol::JSONRPCRequest;
use codex_app_server_protocol::JSONRPCResponse;
use codex_app_server_protocol::LoginChatGptCompleteNotification;
use codex_app_server_protocol::LoginChatGptResponse;
use codex_app_server_protocol::ModelListParams;
use codex_app_server_protocol::ModelListResponse;
use codex_app_server_protocol::NewConversationParams;
use codex_app_server_protocol::NewConversationResponse;
use codex_app_server_protocol::RequestId;
use codex_app_server_protocol::SandboxPolicy;
use codex_app_server_protocol::SendUserMessageParams;
use codex_app_server_protocol::SendUserMessageResponse;
use codex_app_server_protocol::ServerNotification;
use codex_app_server_protocol::ServerRequest;
use codex_app_server_protocol::ThreadStartParams;
use codex_app_server_protocol::ThreadStartResponse;
use codex_app_server_protocol::TurnStartParams;
use codex_app_server_protocol::TurnStartResponse;
use codex_app_server_protocol::TurnStatus;
use codex_app_server_protocol::UserInput as V2UserInput;
use codex_protocol::ThreadId;
use codex_protocol::protocol::Event;
use codex_protocol::protocol::EventMsg;
use serde::Serialize;
use serde::de::DeserializeOwned;
use serde_json::Value;
use uuid::Uuid;
/// Minimal launcher that initializes the Codex app-server and logs the handshake.
#[derive(Parser)]
#[command(author = "Codex", version, about = "Bootstrap Codex app-server", long_about = None)]
struct Cli {
/// Path to the `codex` CLI binary.
#[arg(long, env = "CODEX_BIN", default_value = "codex")]
codex_bin: String,
/// Forwarded to the `codex` CLI as `--config key=value`. Repeatable.
///
/// Example:
/// `--config 'model_providers.mock.base_url="http://localhost:4010/v2"'`
#[arg(
short = 'c',
long = "config",
value_name = "key=value",
action = ArgAction::Append,
global = true
)]
config_overrides: Vec<String>,
#[command(subcommand)]
command: CliCommand,
}
#[derive(Subcommand)]
enum CliCommand {
/// Send a user message through the Codex app-server.
SendMessage {
/// User message to send to Codex.
#[arg()]
user_message: String,
},
/// Send a user message through the app-server V2 thread/turn APIs.
SendMessageV2 {
/// User message to send to Codex.
#[arg()]
user_message: String,
},
/// Start a V2 turn that elicits an ExecCommand approval.
#[command(name = "trigger-cmd-approval")]
TriggerCmdApproval {
/// Optional prompt; defaults to a simple python command.
#[arg()]
user_message: Option<String>,
},
/// Start a V2 turn that elicits an ApplyPatch approval.
#[command(name = "trigger-patch-approval")]
TriggerPatchApproval {
/// Optional prompt; defaults to creating a file via apply_patch.
#[arg()]
user_message: Option<String>,
},
/// Start a V2 turn that should not elicit an ExecCommand approval.
#[command(name = "no-trigger-cmd-approval")]
NoTriggerCmdApproval,
/// Send two sequential V2 turns in the same thread to test follow-up behavior.
SendFollowUpV2 {
/// Initial user message for the first turn.
#[arg()]
first_message: String,
/// Follow-up user message for the second turn.
#[arg()]
follow_up_message: String,
},
/// Trigger the ChatGPT login flow and wait for completion.
TestLogin,
/// Fetch the current account rate limits from the Codex app-server.
GetAccountRateLimits,
/// List the available models from the Codex app-server.
#[command(name = "model-list")]
ModelList,
}
fn main() -> Result<()> {
let Cli {
codex_bin,
config_overrides,
command,
} = Cli::parse();
match command {
CliCommand::SendMessage { user_message } => {
send_message(&codex_bin, &config_overrides, user_message)
}
CliCommand::SendMessageV2 { user_message } => {
send_message_v2(&codex_bin, &config_overrides, user_message)
}
CliCommand::TriggerCmdApproval { user_message } => {
trigger_cmd_approval(&codex_bin, &config_overrides, user_message)
}
CliCommand::TriggerPatchApproval { user_message } => {
trigger_patch_approval(&codex_bin, &config_overrides, user_message)
}
CliCommand::NoTriggerCmdApproval => no_trigger_cmd_approval(&codex_bin, &config_overrides),
CliCommand::SendFollowUpV2 {
first_message,
follow_up_message,
} => send_follow_up_v2(
&codex_bin,
&config_overrides,
first_message,
follow_up_message,
),
CliCommand::TestLogin => test_login(&codex_bin, &config_overrides),
CliCommand::GetAccountRateLimits => get_account_rate_limits(&codex_bin, &config_overrides),
CliCommand::ModelList => model_list(&codex_bin, &config_overrides),
}
}
fn send_message(codex_bin: &str, config_overrides: &[String], user_message: String) -> Result<()> {
let mut client = CodexClient::spawn(codex_bin, config_overrides)?;
let initialize = client.initialize()?;
println!("< initialize response: {initialize:?}");
let conversation = client.start_thread()?;
println!("< newConversation response: {conversation:?}");
let subscription = client.add_conversation_listener(&conversation.conversation_id)?;
println!("< addConversationListener response: {subscription:?}");
let send_response = client.send_user_message(&conversation.conversation_id, &user_message)?;
println!("< sendUserMessage response: {send_response:?}");
client.stream_conversation(&conversation.conversation_id)?;
client.remove_thread_listener(subscription.subscription_id)?;
Ok(())
}
fn send_message_v2(
codex_bin: &str,
config_overrides: &[String],
user_message: String,
) -> Result<()> {
send_message_v2_with_policies(codex_bin, config_overrides, user_message, None, None)
}
fn trigger_cmd_approval(
codex_bin: &str,
config_overrides: &[String],
user_message: Option<String>,
) -> Result<()> {
let default_prompt =
"Run `touch /tmp/should-trigger-approval` so I can confirm the file exists.";
let message = user_message.unwrap_or_else(|| default_prompt.to_string());
send_message_v2_with_policies(
codex_bin,
config_overrides,
message,
Some(AskForApproval::OnRequest),
Some(SandboxPolicy::ReadOnly),
)
}
fn trigger_patch_approval(
codex_bin: &str,
config_overrides: &[String],
user_message: Option<String>,
) -> Result<()> {
let default_prompt =
"Create a file named APPROVAL_DEMO.txt containing a short hello message using apply_patch.";
let message = user_message.unwrap_or_else(|| default_prompt.to_string());
send_message_v2_with_policies(
codex_bin,
config_overrides,
message,
Some(AskForApproval::OnRequest),
Some(SandboxPolicy::ReadOnly),
)
}
fn no_trigger_cmd_approval(codex_bin: &str, config_overrides: &[String]) -> Result<()> {
let prompt = "Run `touch should_not_trigger_approval.txt`";
send_message_v2_with_policies(codex_bin, config_overrides, prompt.to_string(), None, None)
}
fn send_message_v2_with_policies(
codex_bin: &str,
config_overrides: &[String],
user_message: String,
approval_policy: Option<AskForApproval>,
sandbox_policy: Option<SandboxPolicy>,
) -> Result<()> {
let mut client = CodexClient::spawn(codex_bin, config_overrides)?;
let initialize = client.initialize()?;
println!("< initialize response: {initialize:?}");
let thread_response = client.thread_start(ThreadStartParams::default())?;
println!("< thread/start response: {thread_response:?}");
let mut turn_params = TurnStartParams {
thread_id: thread_response.thread.id.clone(),
input: vec![V2UserInput::Text { text: user_message }],
..Default::default()
};
turn_params.approval_policy = approval_policy;
turn_params.sandbox_policy = sandbox_policy;
let turn_response = client.turn_start(turn_params)?;
println!("< turn/start response: {turn_response:?}");
client.stream_turn(&thread_response.thread.id, &turn_response.turn.id)?;
Ok(())
}
fn send_follow_up_v2(
codex_bin: &str,
config_overrides: &[String],
first_message: String,
follow_up_message: String,
) -> Result<()> {
let mut client = CodexClient::spawn(codex_bin, config_overrides)?;
let initialize = client.initialize()?;
println!("< initialize response: {initialize:?}");
let thread_response = client.thread_start(ThreadStartParams::default())?;
println!("< thread/start response: {thread_response:?}");
let first_turn_params = TurnStartParams {
thread_id: thread_response.thread.id.clone(),
input: vec![V2UserInput::Text {
text: first_message,
}],
..Default::default()
};
let first_turn_response = client.turn_start(first_turn_params)?;
println!("< turn/start response (initial): {first_turn_response:?}");
client.stream_turn(&thread_response.thread.id, &first_turn_response.turn.id)?;
let follow_up_params = TurnStartParams {
thread_id: thread_response.thread.id.clone(),
input: vec![V2UserInput::Text {
text: follow_up_message,
}],
..Default::default()
};
let follow_up_response = client.turn_start(follow_up_params)?;
println!("< turn/start response (follow-up): {follow_up_response:?}");
client.stream_turn(&thread_response.thread.id, &follow_up_response.turn.id)?;
Ok(())
}
fn test_login(codex_bin: &str, config_overrides: &[String]) -> Result<()> {
let mut client = CodexClient::spawn(codex_bin, config_overrides)?;
let initialize = client.initialize()?;
println!("< initialize response: {initialize:?}");
let login_response = client.login_chat_gpt()?;
println!("< loginChatGpt response: {login_response:?}");
println!(
"Open the following URL in your browser to continue:\n{}",
login_response.auth_url
);
let completion = client.wait_for_login_completion(&login_response.login_id)?;
println!("< loginChatGptComplete notification: {completion:?}");
if completion.success {
println!("Login succeeded.");
Ok(())
} else {
bail!(
"login failed: {}",
completion
.error
.as_deref()
.unwrap_or("unknown error from loginChatGptComplete")
);
}
}
fn get_account_rate_limits(codex_bin: &str, config_overrides: &[String]) -> Result<()> {
let mut client = CodexClient::spawn(codex_bin, config_overrides)?;
let initialize = client.initialize()?;
println!("< initialize response: {initialize:?}");
let response = client.get_account_rate_limits()?;
println!("< account/rateLimits/read response: {response:?}");
Ok(())
}
fn model_list(codex_bin: &str, config_overrides: &[String]) -> Result<()> {
let mut client = CodexClient::spawn(codex_bin, config_overrides)?;
let initialize = client.initialize()?;
println!("< initialize response: {initialize:?}");
let response = client.model_list(ModelListParams::default())?;
println!("< model/list response: {response:?}");
Ok(())
}
struct CodexClient {
child: Child,
stdin: Option<ChildStdin>,
stdout: BufReader<ChildStdout>,
pending_notifications: VecDeque<JSONRPCNotification>,
}
impl CodexClient {
fn spawn(codex_bin: &str, config_overrides: &[String]) -> Result<Self> {
let mut cmd = Command::new(codex_bin);
for override_kv in config_overrides {
cmd.arg("--config").arg(override_kv);
}
let mut codex_app_server = cmd
.arg("app-server")
.stdin(Stdio::piped())
.stdout(Stdio::piped())
.stderr(Stdio::inherit())
.spawn()
.with_context(|| format!("failed to start `{codex_bin}` app-server"))?;
let stdin = codex_app_server
.stdin
.take()
.context("codex app-server stdin unavailable")?;
let stdout = codex_app_server
.stdout
.take()
.context("codex app-server stdout unavailable")?;
Ok(Self {
child: codex_app_server,
stdin: Some(stdin),
stdout: BufReader::new(stdout),
pending_notifications: VecDeque::new(),
})
}
fn initialize(&mut self) -> Result<InitializeResponse> {
let request_id = self.request_id();
let request = ClientRequest::Initialize {
request_id: request_id.clone(),
params: InitializeParams {
client_info: ClientInfo {
name: "codex-toy-app-server".to_string(),
title: Some("Codex Toy App Server".to_string()),
version: env!("CARGO_PKG_VERSION").to_string(),
},
},
};
self.send_request(request, request_id, "initialize")
}
fn start_thread(&mut self) -> Result<NewConversationResponse> {
let request_id = self.request_id();
let request = ClientRequest::NewConversation {
request_id: request_id.clone(),
params: NewConversationParams::default(),
};
self.send_request(request, request_id, "newConversation")
}
fn add_conversation_listener(
&mut self,
conversation_id: &ThreadId,
) -> Result<AddConversationSubscriptionResponse> {
let request_id = self.request_id();
let request = ClientRequest::AddConversationListener {
request_id: request_id.clone(),
params: AddConversationListenerParams {
conversation_id: *conversation_id,
experimental_raw_events: false,
},
};
self.send_request(request, request_id, "addConversationListener")
}
fn remove_thread_listener(&mut self, subscription_id: Uuid) -> Result<()> {
let request_id = self.request_id();
let request = ClientRequest::RemoveConversationListener {
request_id: request_id.clone(),
params: codex_app_server_protocol::RemoveConversationListenerParams { subscription_id },
};
self.send_request::<codex_app_server_protocol::RemoveConversationSubscriptionResponse>(
request,
request_id,
"removeConversationListener",
)?;
Ok(())
}
fn send_user_message(
&mut self,
conversation_id: &ThreadId,
message: &str,
) -> Result<SendUserMessageResponse> {
let request_id = self.request_id();
let request = ClientRequest::SendUserMessage {
request_id: request_id.clone(),
params: SendUserMessageParams {
conversation_id: *conversation_id,
items: vec![InputItem::Text {
text: message.to_string(),
}],
},
};
self.send_request(request, request_id, "sendUserMessage")
}
fn thread_start(&mut self, params: ThreadStartParams) -> Result<ThreadStartResponse> {
let request_id = self.request_id();
let request = ClientRequest::ThreadStart {
request_id: request_id.clone(),
params,
};
self.send_request(request, request_id, "thread/start")
}
fn turn_start(&mut self, params: TurnStartParams) -> Result<TurnStartResponse> {
let request_id = self.request_id();
let request = ClientRequest::TurnStart {
request_id: request_id.clone(),
params,
};
self.send_request(request, request_id, "turn/start")
}
fn login_chat_gpt(&mut self) -> Result<LoginChatGptResponse> {
let request_id = self.request_id();
let request = ClientRequest::LoginChatGpt {
request_id: request_id.clone(),
params: None,
};
self.send_request(request, request_id, "loginChatGpt")
}
fn get_account_rate_limits(&mut self) -> Result<GetAccountRateLimitsResponse> {
let request_id = self.request_id();
let request = ClientRequest::GetAccountRateLimits {
request_id: request_id.clone(),
params: None,
};
self.send_request(request, request_id, "account/rateLimits/read")
}
fn model_list(&mut self, params: ModelListParams) -> Result<ModelListResponse> {
let request_id = self.request_id();
let request = ClientRequest::ModelList {
request_id: request_id.clone(),
params,
};
self.send_request(request, request_id, "model/list")
}
fn stream_conversation(&mut self, conversation_id: &ThreadId) -> Result<()> {
loop {
let notification = self.next_notification()?;
if !notification.method.starts_with("codex/event/") {
continue;
}
if let Some(event) = self.extract_event(notification, conversation_id)? {
match &event.msg {
EventMsg::AgentMessage(event) => {
println!("{}", event.message);
}
EventMsg::AgentMessageDelta(event) => {
print!("{}", event.delta);
std::io::stdout().flush().ok();
}
EventMsg::TurnComplete(event) => {
println!("\n[task complete: {event:?}]");
break;
}
EventMsg::TurnAborted(event) => {
println!("\n[turn aborted: {:?}]", event.reason);
break;
}
EventMsg::Error(event) => {
println!("[error] {event:?}");
}
_ => {
println!("[UNKNOWN EVENT] {:?}", event.msg);
}
}
}
}
Ok(())
}
fn wait_for_login_completion(
&mut self,
expected_login_id: &Uuid,
) -> Result<LoginChatGptCompleteNotification> {
loop {
let notification = self.next_notification()?;
if let Ok(server_notification) = ServerNotification::try_from(notification) {
match server_notification {
ServerNotification::LoginChatGptComplete(completion) => {
if &completion.login_id == expected_login_id {
return Ok(completion);
}
println!(
"[ignoring loginChatGptComplete for unexpected login_id: {}]",
completion.login_id
);
}
ServerNotification::AuthStatusChange(status) => {
println!("< authStatusChange notification: {status:?}");
}
ServerNotification::AccountRateLimitsUpdated(snapshot) => {
println!("< accountRateLimitsUpdated notification: {snapshot:?}");
}
ServerNotification::SessionConfigured(_) => {
// SessionConfigured notifications are unrelated to login; skip.
}
_ => {}
}
}
// Not a server notification (likely a conversation event); keep waiting.
}
}
fn stream_turn(&mut self, thread_id: &str, turn_id: &str) -> Result<()> {
loop {
let notification = self.next_notification()?;
let Ok(server_notification) = ServerNotification::try_from(notification) else {
continue;
};
match server_notification {
ServerNotification::ThreadStarted(payload) => {
if payload.thread.id == thread_id {
println!("< thread/started notification: {:?}", payload.thread);
}
}
ServerNotification::TurnStarted(payload) => {
if payload.turn.id == turn_id {
println!("< turn/started notification: {:?}", payload.turn.status);
}
}
ServerNotification::AgentMessageDelta(delta) => {
print!("{}", delta.delta);
std::io::stdout().flush().ok();
}
ServerNotification::CommandExecutionOutputDelta(delta) => {
print!("{}", delta.delta);
std::io::stdout().flush().ok();
}
ServerNotification::TerminalInteraction(delta) => {
println!("[stdin sent: {}]", delta.stdin);
std::io::stdout().flush().ok();
}
ServerNotification::ItemStarted(payload) => {
println!("\n< item started: {:?}", payload.item);
}
ServerNotification::ItemCompleted(payload) => {
println!("< item completed: {:?}", payload.item);
}
ServerNotification::TurnCompleted(payload) => {
if payload.turn.id == turn_id {
println!("\n< turn/completed notification: {:?}", payload.turn.status);
if payload.turn.status == TurnStatus::Failed
&& let Some(error) = payload.turn.error
{
println!("[turn error] {}", error.message);
}
break;
}
}
ServerNotification::McpToolCallProgress(payload) => {
println!("< MCP tool progress: {}", payload.message);
}
_ => {
println!("[UNKNOWN SERVER NOTIFICATION] {server_notification:?}");
}
}
}
Ok(())
}
fn extract_event(
&self,
notification: JSONRPCNotification,
conversation_id: &ThreadId,
) -> Result<Option<Event>> {
let params = notification
.params
.context("event notification missing params")?;
let mut map = match params {
Value::Object(map) => map,
other => bail!("unexpected params shape: {other:?}"),
};
let conversation_value = map
.remove("conversationId")
.context("event missing conversationId")?;
let notification_conversation: ThreadId = serde_json::from_value(conversation_value)
.context("conversationId was not a valid UUID")?;
if &notification_conversation != conversation_id {
return Ok(None);
}
let event_value = Value::Object(map);
let event: Event =
serde_json::from_value(event_value).context("failed to decode event payload")?;
Ok(Some(event))
}
fn send_request<T>(
&mut self,
request: ClientRequest,
request_id: RequestId,
method: &str,
) -> Result<T>
where
T: DeserializeOwned,
{
self.write_request(&request)?;
self.wait_for_response(request_id, method)
}
fn write_request(&mut self, request: &ClientRequest) -> Result<()> {
let request_json = serde_json::to_string(request)?;
let request_pretty = serde_json::to_string_pretty(request)?;
print_multiline_with_prefix("> ", &request_pretty);
if let Some(stdin) = self.stdin.as_mut() {
writeln!(stdin, "{request_json}")?;
stdin
.flush()
.context("failed to flush request to codex app-server")?;
} else {
bail!("codex app-server stdin closed");
}
Ok(())
}
fn wait_for_response<T>(&mut self, request_id: RequestId, method: &str) -> Result<T>
where
T: DeserializeOwned,
{
loop {
let message = self.read_jsonrpc_message()?;
match message {
JSONRPCMessage::Response(JSONRPCResponse { id, result }) => {
if id == request_id {
return serde_json::from_value(result)
.with_context(|| format!("{method} response missing payload"));
}
}
JSONRPCMessage::Error(err) => {
if err.id == request_id {
bail!("{method} failed: {err:?}");
}
}
JSONRPCMessage::Notification(notification) => {
self.pending_notifications.push_back(notification);
}
JSONRPCMessage::Request(request) => {
self.handle_server_request(request)?;
}
}
}
}
fn next_notification(&mut self) -> Result<JSONRPCNotification> {
if let Some(notification) = self.pending_notifications.pop_front() {
return Ok(notification);
}
loop {
let message = self.read_jsonrpc_message()?;
match message {
JSONRPCMessage::Notification(notification) => return Ok(notification),
JSONRPCMessage::Response(_) | JSONRPCMessage::Error(_) => {
// No outstanding requests, so ignore stray responses/errors for now.
continue;
}
JSONRPCMessage::Request(request) => {
self.handle_server_request(request)?;
}
}
}
}
fn read_jsonrpc_message(&mut self) -> Result<JSONRPCMessage> {
loop {
let mut response_line = String::new();
let bytes = self
.stdout
.read_line(&mut response_line)
.context("failed to read from codex app-server")?;
if bytes == 0 {
bail!("codex app-server closed stdout");
}
let trimmed = response_line.trim();
if trimmed.is_empty() {
continue;
}
let parsed: Value =
serde_json::from_str(trimmed).context("response was not valid JSON-RPC")?;
let pretty = serde_json::to_string_pretty(&parsed)?;
print_multiline_with_prefix("< ", &pretty);
let message: JSONRPCMessage = serde_json::from_value(parsed)
.context("response was not a valid JSON-RPC message")?;
return Ok(message);
}
}
fn request_id(&self) -> RequestId {
RequestId::String(Uuid::new_v4().to_string())
}
fn handle_server_request(&mut self, request: JSONRPCRequest) -> Result<()> {
let server_request = ServerRequest::try_from(request)
.context("failed to deserialize ServerRequest from JSONRPCRequest")?;
match server_request {
ServerRequest::CommandExecutionRequestApproval { request_id, params } => {
self.handle_command_execution_request_approval(request_id, params)?;
}
ServerRequest::FileChangeRequestApproval { request_id, params } => {
self.approve_file_change_request(request_id, params)?;
}
other => {
bail!("received unsupported server request: {other:?}");
}
}
Ok(())
}
fn handle_command_execution_request_approval(
&mut self,
request_id: RequestId,
params: CommandExecutionRequestApprovalParams,
) -> Result<()> {
let CommandExecutionRequestApprovalParams {
thread_id,
turn_id,
item_id,
reason,
proposed_execpolicy_amendment,
} = params;
println!(
"\n< commandExecution approval requested for thread {thread_id}, turn {turn_id}, item {item_id}"
);
if let Some(reason) = reason.as_deref() {
println!("< reason: {reason}");
}
if let Some(execpolicy_amendment) = proposed_execpolicy_amendment.as_ref() {
println!("< proposed execpolicy amendment: {execpolicy_amendment:?}");
}
let response = CommandExecutionRequestApprovalResponse {
decision: CommandExecutionApprovalDecision::Accept,
};
self.send_server_request_response(request_id, &response)?;
println!("< approved commandExecution request for item {item_id}");
Ok(())
}
fn approve_file_change_request(
&mut self,
request_id: RequestId,
params: FileChangeRequestApprovalParams,
) -> Result<()> {
let FileChangeRequestApprovalParams {
thread_id,
turn_id,
item_id,
reason,
grant_root,
} = params;
println!(
"\n< fileChange approval requested for thread {thread_id}, turn {turn_id}, item {item_id}"
);
if let Some(reason) = reason.as_deref() {
println!("< reason: {reason}");
}
if let Some(grant_root) = grant_root.as_deref() {
println!("< grant root: {}", grant_root.display());
}
let response = FileChangeRequestApprovalResponse {
decision: FileChangeApprovalDecision::Accept,
};
self.send_server_request_response(request_id, &response)?;
println!("< approved fileChange request for item {item_id}");
Ok(())
}
fn send_server_request_response<T>(&mut self, request_id: RequestId, response: &T) -> Result<()>
where
T: Serialize,
{
let message = JSONRPCMessage::Response(JSONRPCResponse {
id: request_id,
result: serde_json::to_value(response)?,
});
self.write_jsonrpc_message(message)
}
fn write_jsonrpc_message(&mut self, message: JSONRPCMessage) -> Result<()> {
let payload = serde_json::to_string(&message)?;
let pretty = serde_json::to_string_pretty(&message)?;
print_multiline_with_prefix("> ", &pretty);
if let Some(stdin) = self.stdin.as_mut() {
writeln!(stdin, "{payload}")?;
stdin
.flush()
.context("failed to flush response to codex app-server")?;
return Ok(());
}
bail!("codex app-server stdin closed")
}
}
fn print_multiline_with_prefix(prefix: &str, payload: &str) {
for line in payload.lines() {
println!("{prefix}{line}");
}
}
impl Drop for CodexClient {
fn drop(&mut self) {
let _ = self.stdin.take();
if let Ok(Some(status)) = self.child.try_wait() {
println!("[codex app-server exited: {status}]");
return;
}
thread::sleep(Duration::from_millis(100));
if let Ok(Some(status)) = self.child.try_wait() {
println!("[codex app-server exited: {status}]");
return;
}
let _ = self.child.kill();
let _ = self.child.wait();
}
}

View File

@@ -0,0 +1,8 @@
load("//:defs.bzl", "codex_rust_crate")
codex_rust_crate(
name = "app-server",
crate_name = "codex_app_server",
integration_deps_extra = ["//codex-rs/app-server/tests/common:common"],
test_tags = ["no-sandbox"],
)

View File

@@ -1,7 +1,8 @@
[package]
edition = "2024"
name = "codex-app-server"
version = { workspace = true }
version.workspace = true
edition.workspace = true
license.workspace = true
[[bin]]
name = "codex-app-server"
@@ -24,10 +25,16 @@ codex-file-search = { workspace = true }
codex-login = { workspace = true }
codex-protocol = { workspace = true }
codex-app-server-protocol = { workspace = true }
codex-feedback = { workspace = true }
codex-rmcp-client = { workspace = true }
codex-utils-absolute-path = { workspace = true }
codex-utils-json-to-toml = { workspace = true }
chrono = { workspace = true }
serde = { workspace = true, features = ["derive"] }
serde_json = { workspace = true }
mcp-types = { workspace = true }
tempfile = { workspace = true }
toml = { workspace = true }
tokio = { workspace = true, features = [
"io-std",
"macros",
@@ -37,16 +44,15 @@ tokio = { workspace = true, features = [
] }
tracing = { workspace = true, features = ["log"] }
tracing-subscriber = { workspace = true, features = ["env-filter", "fmt"] }
opentelemetry-appender-tracing = { workspace = true }
uuid = { workspace = true, features = ["serde", "v7"] }
[dev-dependencies]
app_test_support = { workspace = true }
assert_cmd = { workspace = true }
base64 = { workspace = true }
core_test_support = { workspace = true }
mcp-types = { workspace = true }
os_info = { workspace = true }
pretty_assertions = { workspace = true }
tempfile = { workspace = true }
toml = { workspace = true }
serial_test = { workspace = true }
wiremock = { workspace = true }
shlex = { workspace = true }

View File

@@ -1,6 +1,19 @@
# codex-app-server
`codex app-server` is the harness Codex uses to power rich interfaces such as the [Codex VS Code extension](https://marketplace.visualstudio.com/items?itemName=openai.chatgpt). The message schema is currently unstable, but those who wish to build experimental UIs on top of Codex may find it valuable.
`codex app-server` is the interface Codex uses to power rich interfaces such as the [Codex VS Code extension](https://marketplace.visualstudio.com/items?itemName=openai.chatgpt).
## Table of Contents
- [Protocol](#protocol)
- [Message Schema](#message-schema)
- [Core Primitives](#core-primitives)
- [Lifecycle Overview](#lifecycle-overview)
- [Initialization](#initialization)
- [API Overview](#api-overview)
- [Events](#events)
- [Approvals](#approvals)
- [Skills](#skills)
- [Auth endpoints](#auth-endpoints)
## Protocol
@@ -8,8 +21,575 @@ Similar to [MCP](https://modelcontextprotocol.io/), `codex app-server` supports
## Message Schema
Currently, you can dump a TypeScript version of the schema using `codex generate-ts`. It is specific to the version of Codex you used to run `generate-ts`, so the two are guaranteed to be compatible.
Currently, you can dump a TypeScript version of the schema using `codex app-server generate-ts`, or a JSON Schema bundle via `codex app-server generate-json-schema`. Each output is specific to the version of Codex you used to run the command, so the generated artifacts are guaranteed to match that version.
```
codex generate-ts --out DIR
codex app-server generate-ts --out DIR
codex app-server generate-json-schema --out DIR
```
## Core Primitives
The API exposes three top level primitives representing an interaction between a user and Codex:
- **Thread**: A conversation between a user and the Codex agent. Each thread contains multiple turns.
- **Turn**: One turn of the conversation, typically starting with a user message and finishing with an agent message. Each turn contains multiple items.
- **Item**: Represents user inputs and agent outputs as part of the turn, persisted and used as the context for future conversations. Example items include user message, agent reasoning, agent message, shell command, file edit, etc.
Use the thread APIs to create, list, or archive conversations. Drive a conversation with turn APIs and stream progress via turn notifications.
## Lifecycle Overview
- Initialize once: Immediately after launching the codex app-server process, send an `initialize` request with your client metadata, then emit an `initialized` notification. Any other request before this handshake gets rejected.
- Start (or resume) a thread: Call `thread/start` to open a fresh conversation. The response returns the thread object and youll also get a `thread/started` notification. If youre continuing an existing conversation, call `thread/resume` with its ID instead. If you want to branch from an existing conversation, call `thread/fork` to create a new thread id with copied history.
- Begin a turn: To send user input, call `turn/start` with the target `threadId` and the user's input. Optional fields let you override model, cwd, sandbox policy, etc. This immediately returns the new turn object and triggers a `turn/started` notification.
- Stream events: After `turn/start`, keep reading JSON-RPC notifications on stdout. Youll see `item/started`, `item/completed`, deltas like `item/agentMessage/delta`, tool progress, etc. These represent streaming model output plus any side effects (commands, tool calls, reasoning notes).
- Finish the turn: When the model is done (or the turn is interrupted via making the `turn/interrupt` call), the server sends `turn/completed` with the final turn state and token usage.
## Initialization
Clients must send a single `initialize` request before invoking any other method, then acknowledge with an `initialized` notification. The server returns the user agent string it will present to upstream services; subsequent requests issued before initialization receive a `"Not initialized"` error, and repeated `initialize` calls receive an `"Already initialized"` error.
Applications building on top of `codex app-server` should identify themselves via the `clientInfo` parameter.
**Important**: `clientInfo.name` is used to identify the client for the OpenAI Compliance Logs Platform. If
you are developing a new Codex integration that is intended for enterprise use, please contact us to get it
added to a known clients list. For more context: https://chatgpt.com/admin/api-reference#tag/Logs:-Codex
Example (from OpenAI's official VSCode extension):
```json
{
"method": "initialize",
"id": 0,
"params": {
"clientInfo": {
"name": "codex_vscode",
"title": "Codex VS Code Extension",
"version": "0.1.0"
}
}
}
```
## API Overview
- `thread/start` — create a new thread; emits `thread/started` and auto-subscribes you to turn/item events for that thread.
- `thread/resume` — reopen an existing thread by id so subsequent `turn/start` calls append to it.
- `thread/fork` — fork an existing thread into a new thread id by copying the stored history; emits `thread/started` and auto-subscribes you to turn/item events for the new thread.
- `thread/list` — page through stored rollouts; supports cursor-based pagination and optional `modelProviders` filtering.
- `thread/loaded/list` — list the thread ids currently loaded in memory.
- `thread/archive` — move a threads rollout file into the archived directory; returns `{}` on success.
- `thread/rollback` — drop the last N turns from the agents in-memory context and persist a rollback marker in the rollout so future resumes see the pruned history; returns the updated `thread` (with `turns` populated) on success.
- `turn/start` — add user input to a thread and begin Codex generation; responds with the initial `turn` object and streams `turn/started`, `item/*`, and `turn/completed` notifications.
- `turn/interrupt` — request cancellation of an in-flight turn by `(thread_id, turn_id)`; success is an empty `{}` response and the turn finishes with `status: "interrupted"`.
- `review/start` — kick off Codexs automated reviewer for a thread; responds like `turn/start` and emits `item/started`/`item/completed` notifications with `enteredReviewMode` and `exitedReviewMode` items, plus a final assistant `agentMessage` containing the review.
- `command/exec` — run a single command under the server sandbox without starting a thread/turn (handy for utilities and validation).
- `model/list` — list available models (with reasoning effort options).
- `skills/list` — list skills for one or more `cwd` values (optional `forceReload`).
- `mcpServer/oauth/login` — start an OAuth login for a configured MCP server; returns an `authorization_url` and later emits `mcpServer/oauthLogin/completed` once the browser flow finishes.
- `mcpServerStatus/list` — enumerate configured MCP servers with their tools, resources, resource templates, and auth status; supports cursor+limit pagination.
- `feedback/upload` — submit a feedback report (classification + optional reason/logs and conversation_id); returns the tracking thread id.
- `command/exec` — run a single command under the server sandbox without starting a thread/turn (handy for utilities and validation).
- `config/read` — fetch the effective config on disk after resolving config layering.
- `config/value/write` — write a single config key/value to the user's config.toml on disk.
- `config/batchWrite` — apply multiple config edits atomically to the user's config.toml on disk.
- `configRequirements/read` — fetch the loaded requirements allow-lists from `requirements.toml` and/or MDM (or `null` if none are configured).
### Example: Start or resume a thread
Start a fresh thread when you need a new Codex conversation.
```json
{ "method": "thread/start", "id": 10, "params": {
// Optionally set config settings. If not specified, will use the user's
// current config settings.
"model": "gpt-5.1-codex",
"cwd": "/Users/me/project",
"approvalPolicy": "never",
"sandbox": "workspaceWrite",
} }
{ "id": 10, "result": {
"thread": {
"id": "thr_123",
"preview": "",
"modelProvider": "openai",
"createdAt": 1730910000
}
} }
{ "method": "thread/started", "params": { "thread": { } } }
```
To continue a stored session, call `thread/resume` with the `thread.id` you previously recorded. The response shape matches `thread/start`, and no additional notifications are emitted:
```json
{ "method": "thread/resume", "id": 11, "params": { "threadId": "thr_123" } }
{ "id": 11, "result": { "thread": { "id": "thr_123", } } }
```
To branch from a stored session, call `thread/fork` with the `thread.id`. This creates a new thread id and emits a `thread/started` notification for it:
```json
{ "method": "thread/fork", "id": 12, "params": { "threadId": "thr_123" } }
{ "id": 12, "result": { "thread": { "id": "thr_456", } } }
{ "method": "thread/started", "params": { "thread": { } } }
```
### Example: List threads (with pagination & filters)
`thread/list` lets you render a history UI. Pass any combination of:
- `cursor` — opaque string from a prior response; omit for the first page.
- `limit` — server defaults to a reasonable page size if unset.
- `modelProviders` — restrict results to specific providers; unset, null, or an empty array will include all providers.
Example:
```json
{ "method": "thread/list", "id": 20, "params": {
"cursor": null,
"limit": 25,
} }
{ "id": 20, "result": {
"data": [
{ "id": "thr_a", "preview": "Create a TUI", "modelProvider": "openai", "createdAt": 1730831111 },
{ "id": "thr_b", "preview": "Fix tests", "modelProvider": "openai", "createdAt": 1730750000 }
],
"nextCursor": "opaque-token-or-null"
} }
```
When `nextCursor` is `null`, youve reached the final page.
### Example: List loaded threads
`thread/loaded/list` returns thread ids currently loaded in memory. This is useful when you want to check which sessions are active without scanning rollouts on disk.
```json
{ "method": "thread/loaded/list", "id": 21 }
{ "id": 21, "result": {
"data": ["thr_123", "thr_456"]
} }
```
### Example: Archive a thread
Use `thread/archive` to move the persisted rollout (stored as a JSONL file on disk) into the archived sessions directory.
```json
{ "method": "thread/archive", "id": 21, "params": { "threadId": "thr_b" } }
{ "id": 21, "result": {} }
```
An archived thread will not appear in future calls to `thread/list`.
### Example: Start a turn (send user input)
Turns attach user input (text or images) to a thread and trigger Codex generation. The `input` field is a list of discriminated unions:
- `{"type":"text","text":"Explain this diff"}`
- `{"type":"image","url":"https://…png"}`
- `{"type":"localImage","path":"/tmp/screenshot.png"}`
You can optionally specify config overrides on the new turn. If specified, these settings become the default for subsequent turns on the same thread. `outputSchema` applies only to the current turn.
```json
{ "method": "turn/start", "id": 30, "params": {
"threadId": "thr_123",
"input": [ { "type": "text", "text": "Run tests" } ],
// Below are optional config overrides
"cwd": "/Users/me/project",
"approvalPolicy": "unlessTrusted",
"sandboxPolicy": {
"type": "workspaceWrite",
"writableRoots": ["/Users/me/project"],
"networkAccess": true
},
"model": "gpt-5.1-codex",
"effort": "medium",
"summary": "concise",
// Optional JSON Schema to constrain the final assistant message for this turn.
"outputSchema": {
"type": "object",
"properties": { "answer": { "type": "string" } },
"required": ["answer"],
"additionalProperties": false
}
} }
{ "id": 30, "result": { "turn": {
"id": "turn_456",
"status": "inProgress",
"items": [],
"error": null
} } }
```
### Example: Start a turn (invoke a skill)
Invoke a skill explicitly by including `$<skill-name>` in the text input and adding a `skill` input item alongside it.
```json
{ "method": "turn/start", "id": 33, "params": {
"threadId": "thr_123",
"input": [
{ "type": "text", "text": "$skill-creator Add a new skill for triaging flaky CI and include step-by-step usage." },
{ "type": "skill", "name": "skill-creator", "path": "/Users/me/.codex/skills/skill-creator/SKILL.md" }
]
} }
{ "id": 33, "result": { "turn": {
"id": "turn_457",
"status": "inProgress",
"items": [],
"error": null
} } }
```
### Example: Interrupt an active turn
You can cancel a running Turn with `turn/interrupt`.
```json
{ "method": "turn/interrupt", "id": 31, "params": {
"threadId": "thr_123",
"turnId": "turn_456"
} }
{ "id": 31, "result": {} }
```
The server requests cancellations for running subprocesses, then emits a `turn/completed` event with `status: "interrupted"`. Rely on the `turn/completed` to know when Codex-side cleanup is done.
### Example: Request a code review
Use `review/start` to run Codexs reviewer on the currently checked-out project. The request takes the thread id plus a `target` describing what should be reviewed:
- `{"type":"uncommittedChanges"}` — staged, unstaged, and untracked files.
- `{"type":"baseBranch","branch":"main"}` — diff against the provided branchs upstream (see prompt for the exact `git merge-base`/`git diff` instructions Codex will run).
- `{"type":"commit","sha":"abc1234","title":"Optional subject"}` — review a specific commit.
- `{"type":"custom","instructions":"Free-form reviewer instructions"}` — fallback prompt equivalent to the legacy manual review request.
- `delivery` (`"inline"` or `"detached"`, default `"inline"`) — where the review runs:
- `"inline"`: run the review as a new turn on the existing thread. The responses `reviewThreadId` equals the original `threadId`, and no new `thread/started` notification is emitted.
- `"detached"`: fork a new review thread from the parent conversation and run the review there. The responses `reviewThreadId` is the id of this new review thread, and the server emits a `thread/started` notification for it before streaming review items.
Example request/response:
```json
{ "method": "review/start", "id": 40, "params": {
"threadId": "thr_123",
"delivery": "inline",
"target": { "type": "commit", "sha": "1234567deadbeef", "title": "Polish tui colors" }
} }
{ "id": 40, "result": {
"turn": {
"id": "turn_900",
"status": "inProgress",
"items": [
{ "type": "userMessage", "id": "turn_900", "content": [ { "type": "text", "text": "Review commit 1234567: Polish tui colors" } ] }
],
"error": null
},
"reviewThreadId": "thr_123"
} }
```
For a detached review, use `"delivery": "detached"`. The response is the same shape, but `reviewThreadId` will be the id of the new review thread (different from the original `threadId`). The server also emits a `thread/started` notification for that new thread before streaming the review turn.
Codex streams the usual `turn/started` notification followed by an `item/started`
with an `enteredReviewMode` item so clients can show progress:
```json
{
"method": "item/started",
"params": {
"item": {
"type": "enteredReviewMode",
"id": "turn_900",
"review": "current changes"
}
}
}
```
When the reviewer finishes, the server emits `item/started` and `item/completed`
containing an `exitedReviewMode` item with the final review text:
```json
{
"method": "item/completed",
"params": {
"item": {
"type": "exitedReviewMode",
"id": "turn_900",
"review": "Looks solid overall...\n\n- Prefer Stylize helpers — app.rs:10-20\n ..."
}
}
}
```
The `review` string is plain text that already bundles the overall explanation plus a bullet list for each structured finding (matching `ThreadItem::ExitedReviewMode` in the generated schema). Use this notification to render the reviewer output in your client.
### Example: One-off command execution
Run a standalone command (argv vector) in the servers sandbox without creating a thread or turn:
```json
{ "method": "command/exec", "id": 32, "params": {
"command": ["ls", "-la"],
"cwd": "/Users/me/project", // optional; defaults to server cwd
"sandboxPolicy": { "type": "workspaceWrite" }, // optional; defaults to user config
"timeoutMs": 10000 // optional; ms timeout; defaults to server timeout
} }
{ "id": 32, "result": { "exitCode": 0, "stdout": "...", "stderr": "" } }
```
- For clients that are already sandboxed externally, set `sandboxPolicy` to `{"type":"externalSandbox","networkAccess":"enabled"}` (or omit `networkAccess` to keep it restricted). Codex will not enforce its own sandbox in this mode; it tells the model it has full file-system access and passes the `networkAccess` state through `environment_context`.
Notes:
- Empty `command` arrays are rejected.
- `sandboxPolicy` accepts the same shape used by `turn/start` (e.g., `dangerFullAccess`, `readOnly`, `workspaceWrite` with flags, `externalSandbox` with `networkAccess` `restricted|enabled`).
- When omitted, `timeoutMs` falls back to the server default.
## Events
Event notifications are the server-initiated event stream for thread lifecycles, turn lifecycles, and the items within them. After you start or resume a thread, keep reading stdout for `thread/started`, `turn/*`, and `item/*` notifications.
### Turn events
The app-server streams JSON-RPC notifications while a turn is running. Each turn starts with `turn/started` (initial `turn`) and ends with `turn/completed` (final `turn` status). Token usage events stream separately via `thread/tokenUsage/updated`. Clients subscribe to the events they care about, rendering each item incrementally as updates arrive. The per-item lifecycle is always: `item/started` → zero or more item-specific deltas → `item/completed`.
- `turn/started``{ turn }` with the turn id, empty `items`, and `status: "inProgress"`.
- `turn/completed``{ turn }` where `turn.status` is `completed`, `interrupted`, or `failed`; failures carry `{ error: { message, codexErrorInfo?, additionalDetails? } }`.
- `turn/diff/updated``{ threadId, turnId, diff }` represents the up-to-date snapshot of the turn-level unified diff, emitted after every FileChange item. `diff` is the latest aggregated unified diff across every file change in the turn. UIs can render this to show the full "what changed" view without stitching individual `fileChange` items.
- `turn/plan/updated``{ turnId, explanation?, plan }` whenever the agent shares or changes its plan; each `plan` entry is `{ step, status }` with `status` in `pending`, `inProgress`, or `completed`.
Today both notifications carry an empty `items` array even when item events were streamed; rely on `item/*` notifications for the canonical item list until this is fixed.
#### Items
`ThreadItem` is the tagged union carried in turn responses and `item/*` notifications. Currently we support events for the following items:
- `userMessage``{id, content}` where `content` is a list of user inputs (`text`, `image`, or `localImage`).
- `agentMessage``{id, text}` containing the accumulated agent reply.
- `reasoning``{id, summary, content}` where `summary` holds streamed reasoning summaries (applicable for most OpenAI models) and `content` holds raw reasoning blocks (applicable for e.g. open source models).
- `commandExecution``{id, command, cwd, status, commandActions, aggregatedOutput?, exitCode?, durationMs?}` for sandboxed commands; `status` is `inProgress`, `completed`, `failed`, or `declined`.
- `fileChange``{id, changes, status}` describing proposed edits; `changes` list `{path, kind, diff}` and `status` is `inProgress`, `completed`, `failed`, or `declined`.
- `mcpToolCall``{id, server, tool, status, arguments, result?, error?}` describing MCP calls; `status` is `inProgress`, `completed`, or `failed`.
- `webSearch``{id, query}` for a web search request issued by the agent.
- `imageView``{id, path}` emitted when the agent invokes the image viewer tool.
- `enteredReviewMode``{id, review}` sent when the reviewer starts; `review` is a short user-facing label such as `"current changes"` or the requested target description.
- `exitedReviewMode``{id, review}` emitted when the reviewer finishes; `review` is the full plain-text review (usually, overall notes plus bullet point findings).
- `compacted` - `{threadId, turnId}` when codex compacts the conversation history. This can happen automatically.
All items emit two shared lifecycle events:
- `item/started` — emits the full `item` when a new unit of work begins so the UI can render it immediately; the `item.id` in this payload matches the `itemId` used by deltas.
- `item/completed` — sends the final `item` once that work finishes (e.g., after a tool call or message completes); treat this as the authoritative state.
There are additional item-specific events:
#### agentMessage
- `item/agentMessage/delta` — appends streamed text for the agent message; concatenate `delta` values for the same `itemId` in order to reconstruct the full reply.
#### reasoning
- `item/reasoning/summaryTextDelta` — streams readable reasoning summaries; `summaryIndex` increments when a new summary section opens.
- `item/reasoning/summaryPartAdded` — marks the boundary between reasoning summary sections for an `itemId`; subsequent `summaryTextDelta` entries share the same `summaryIndex`.
- `item/reasoning/textDelta` — streams raw reasoning text (only applicable for e.g. open source models); use `contentIndex` to group deltas that belong together before showing them in the UI.
#### commandExecution
- `item/commandExecution/outputDelta` — streams stdout/stderr for the command; append deltas in order to render live output alongside `aggregatedOutput` in the final item.
Final `commandExecution` items include parsed `commandActions`, `status`, `exitCode`, and `durationMs` so the UI can summarize what ran and whether it succeeded.
#### fileChange
- `item/fileChange/outputDelta` - contains the tool call response of the underlying `apply_patch` tool call.
### Errors
`error` event is emitted whenever the server hits an error mid-turn (for example, upstream model errors or quota limits). Carries the same `{ error: { message, codexErrorInfo?, additionalDetails? } }` payload as `turn.status: "failed"` and may precede that terminal notification.
`codexErrorInfo` maps to the `CodexErrorInfo` enum. Common values:
- `ContextWindowExceeded`
- `UsageLimitExceeded`
- `HttpConnectionFailed { httpStatusCode? }`: upstream HTTP failures including 4xx/5xx
- `ResponseStreamConnectionFailed { httpStatusCode? }`: failure to connect to the response SSE stream
- `ResponseStreamDisconnected { httpStatusCode? }`: disconnect of the response SSE stream in the middle of a turn before completion
- `ResponseTooManyFailedAttempts { httpStatusCode? }`
- `BadRequest`
- `Unauthorized`
- `SandboxError`
- `InternalServerError`
- `Other`: all unclassified errors
When an upstream HTTP status is available (for example, from the Responses API or a provider), it is forwarded in `httpStatusCode` on the relevant `codexErrorInfo` variant.
## Approvals
Certain actions (shell commands or modifying files) may require explicit user approval depending on the user's config. When `turn/start` is used, the app-server drives an approval flow by sending a server-initiated JSON-RPC request to the client. The client must respond to tell Codex whether to proceed. UIs should present these requests inline with the active turn so users can review the proposed command or diff before choosing.
- Requests include `threadId` and `turnId`—use them to scope UI state to the active conversation.
- Respond with a single `{ "decision": "accept" | "decline" }` payload (plus optional `acceptSettings` on command executions). The server resumes or declines the work and ends the item with `item/completed`.
### Command execution approvals
Order of messages:
1. `item/started` — shows the pending `commandExecution` item with `command`, `cwd`, and other fields so you can render the proposed action.
2. `item/commandExecution/requestApproval` (request) — carries the same `itemId`, `threadId`, `turnId`, optionally `reason` or `risk`, plus `parsedCmd` for friendly display.
3. Client response — `{ "decision": "accept", "acceptSettings": { "forSession": false } }` or `{ "decision": "decline" }`.
4. `item/completed` — final `commandExecution` item with `status: "completed" | "failed" | "declined"` and execution output. Render this as the authoritative result.
### File change approvals
Order of messages:
1. `item/started` — emits a `fileChange` item with `changes` (diff chunk summaries) and `status: "inProgress"`. Show the proposed edits and paths to the user.
2. `item/fileChange/requestApproval` (request) — includes `itemId`, `threadId`, `turnId`, and an optional `reason`.
3. Client response — `{ "decision": "accept" }` or `{ "decision": "decline" }`.
4. `item/completed` — returns the same `fileChange` item with `status` updated to `completed`, `failed`, or `declined` after the patch attempt. Rely on this to show success/failure and finalize the diff state in your UI.
UI guidance for IDEs: surface an approval dialog as soon as the request arrives. The turn will proceed after the server receives a response to the approval request. The terminal `item/completed` notification will be sent with the appropriate status.
## Skills
Invoke a skill by including `$<skill-name>` in the text input. Add a `skill` input item (recommended) so the backend injects full skill instructions instead of relying on the model to resolve the name.
```json
{
"method": "turn/start",
"id": 101,
"params": {
"threadId": "thread-1",
"input": [
{ "type": "text", "text": "$skill-creator Add a new skill for triaging flaky CI." },
{ "type": "skill", "name": "skill-creator", "path": "/Users/me/.codex/skills/skill-creator/SKILL.md" }
]
}
}
```
If you omit the `skill` item, the model will still parse the `$<skill-name>` marker and try to locate the skill, which can add latency.
Example:
```
$skill-creator Add a new skill for triaging flaky CI and include step-by-step usage.
```
Use `skills/list` to fetch the available skills (optionally scoped by `cwd` and/or with `forceReload`).
```json
{ "method": "skills/list", "id": 25, "params": {
"cwd": "/Users/me/project",
"forceReload": false
} }
{ "id": 25, "result": {
"skills": [
{ "name": "skill-creator", "description": "Create or update a Codex skill" }
]
} }
```
## Auth endpoints
The JSON-RPC auth/account surface exposes request/response methods plus server-initiated notifications (no `id`). Use these to determine auth state, start or cancel logins, logout, and inspect ChatGPT rate limits.
### API Overview
- `account/read` — fetch current account info; optionally refresh tokens.
- `account/login/start` — begin login (`apiKey` or `chatgpt`).
- `account/login/completed` (notify) — emitted when a login attempt finishes (success or error).
- `account/login/cancel` — cancel a pending ChatGPT login by `loginId`.
- `account/logout` — sign out; triggers `account/updated`.
- `account/updated` (notify) — emitted whenever auth mode changes (`authMode`: `apikey`, `chatgpt`, or `null`).
- `account/rateLimits/read` — fetch ChatGPT rate limits; updates arrive via `account/rateLimits/updated` (notify).
- `account/rateLimits/updated` (notify) — emitted whenever a user's ChatGPT rate limits change.
- `mcpServer/oauthLogin/completed` (notify) — emitted after a `mcpServer/oauth/login` flow finishes for a server; payload includes `{ name, success, error? }`.
### 1) Check auth state
Request:
```json
{ "method": "account/read", "id": 1, "params": { "refreshToken": false } }
```
Response examples:
```json
{ "id": 1, "result": { "account": null, "requiresOpenaiAuth": false } } // No OpenAI auth needed (e.g., OSS/local models)
{ "id": 1, "result": { "account": null, "requiresOpenaiAuth": true } } // OpenAI auth required (typical for OpenAI-hosted models)
{ "id": 1, "result": { "account": { "type": "apiKey" }, "requiresOpenaiAuth": true } }
{ "id": 1, "result": { "account": { "type": "chatgpt", "email": "user@example.com", "planType": "pro" }, "requiresOpenaiAuth": true } }
```
Field notes:
- `refreshToken` (bool): set `true` to force a token refresh.
- `requiresOpenaiAuth` reflects the active provider; when `false`, Codex can run without OpenAI credentials.
### 2) Log in with an API key
1. Send:
```json
{
"method": "account/login/start",
"id": 2,
"params": { "type": "apiKey", "apiKey": "sk-…" }
}
```
2. Expect:
```json
{ "id": 2, "result": { "type": "apiKey" } }
```
3. Notifications:
```json
{ "method": "account/login/completed", "params": { "loginId": null, "success": true, "error": null } }
{ "method": "account/updated", "params": { "authMode": "apikey" } }
```
### 3) Log in with ChatGPT (browser flow)
1. Start:
```json
{ "method": "account/login/start", "id": 3, "params": { "type": "chatgpt" } }
{ "id": 3, "result": { "type": "chatgpt", "loginId": "<uuid>", "authUrl": "https://chatgpt.com/…&redirect_uri=http%3A%2F%2Flocalhost%3A<port>%2Fauth%2Fcallback" } }
```
2. Open `authUrl` in a browser; the app-server hosts the local callback.
3. Wait for notifications:
```json
{ "method": "account/login/completed", "params": { "loginId": "<uuid>", "success": true, "error": null } }
{ "method": "account/updated", "params": { "authMode": "chatgpt" } }
```
### 4) Cancel a ChatGPT login
```json
{ "method": "account/login/cancel", "id": 4, "params": { "loginId": "<uuid>" } }
{ "method": "account/login/completed", "params": { "loginId": "<uuid>", "success": false, "error": "…" } }
```
### 5) Logout
```json
{ "method": "account/logout", "id": 5 }
{ "id": 5, "result": {} }
{ "method": "account/updated", "params": { "authMode": null } }
```
### 6) Rate limits (ChatGPT)
```json
{ "method": "account/rateLimits/read", "id": 6 }
{ "id": 6, "result": { "rateLimits": { "primary": { "usedPercent": 25, "windowDurationMins": 15, "resetsAt": 1730947200 }, "secondary": null } } }
{ "method": "account/rateLimits/updated", "params": { "rateLimits": { … } } }
```
Field notes:
- `usedPercent` is current usage within the OpenAI quota window.
- `windowDurationMins` is the quota window length.
- `resetsAt` is a Unix timestamp (seconds) for the next reset.

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,154 @@
use crate::error_code::INTERNAL_ERROR_CODE;
use crate::error_code::INVALID_REQUEST_ERROR_CODE;
use codex_app_server_protocol::ConfigBatchWriteParams;
use codex_app_server_protocol::ConfigReadParams;
use codex_app_server_protocol::ConfigReadResponse;
use codex_app_server_protocol::ConfigRequirements;
use codex_app_server_protocol::ConfigRequirementsReadResponse;
use codex_app_server_protocol::ConfigValueWriteParams;
use codex_app_server_protocol::ConfigWriteErrorCode;
use codex_app_server_protocol::ConfigWriteResponse;
use codex_app_server_protocol::JSONRPCErrorError;
use codex_app_server_protocol::SandboxMode;
use codex_core::config::ConfigService;
use codex_core::config::ConfigServiceError;
use codex_core::config_loader::ConfigRequirementsToml;
use codex_core::config_loader::LoaderOverrides;
use codex_core::config_loader::SandboxModeRequirement as CoreSandboxModeRequirement;
use serde_json::json;
use std::path::PathBuf;
use toml::Value as TomlValue;
#[derive(Clone)]
pub(crate) struct ConfigApi {
service: ConfigService,
}
impl ConfigApi {
pub(crate) fn new(
codex_home: PathBuf,
cli_overrides: Vec<(String, TomlValue)>,
loader_overrides: LoaderOverrides,
) -> Self {
Self {
service: ConfigService::new(codex_home, cli_overrides, loader_overrides),
}
}
pub(crate) async fn read(
&self,
params: ConfigReadParams,
) -> Result<ConfigReadResponse, JSONRPCErrorError> {
self.service.read(params).await.map_err(map_error)
}
pub(crate) async fn config_requirements_read(
&self,
) -> Result<ConfigRequirementsReadResponse, JSONRPCErrorError> {
let requirements = self
.service
.read_requirements()
.await
.map_err(map_error)?
.map(map_requirements_toml_to_api);
Ok(ConfigRequirementsReadResponse { requirements })
}
pub(crate) async fn write_value(
&self,
params: ConfigValueWriteParams,
) -> Result<ConfigWriteResponse, JSONRPCErrorError> {
self.service.write_value(params).await.map_err(map_error)
}
pub(crate) async fn batch_write(
&self,
params: ConfigBatchWriteParams,
) -> Result<ConfigWriteResponse, JSONRPCErrorError> {
self.service.batch_write(params).await.map_err(map_error)
}
}
fn map_requirements_toml_to_api(requirements: ConfigRequirementsToml) -> ConfigRequirements {
ConfigRequirements {
allowed_approval_policies: requirements.allowed_approval_policies.map(|policies| {
policies
.into_iter()
.map(codex_app_server_protocol::AskForApproval::from)
.collect()
}),
allowed_sandbox_modes: requirements.allowed_sandbox_modes.map(|modes| {
modes
.into_iter()
.filter_map(map_sandbox_mode_requirement_to_api)
.collect()
}),
}
}
fn map_sandbox_mode_requirement_to_api(mode: CoreSandboxModeRequirement) -> Option<SandboxMode> {
match mode {
CoreSandboxModeRequirement::ReadOnly => Some(SandboxMode::ReadOnly),
CoreSandboxModeRequirement::WorkspaceWrite => Some(SandboxMode::WorkspaceWrite),
CoreSandboxModeRequirement::DangerFullAccess => Some(SandboxMode::DangerFullAccess),
CoreSandboxModeRequirement::ExternalSandbox => None,
}
}
fn map_error(err: ConfigServiceError) -> JSONRPCErrorError {
if let Some(code) = err.write_error_code() {
return config_write_error(code, err.to_string());
}
JSONRPCErrorError {
code: INTERNAL_ERROR_CODE,
message: err.to_string(),
data: None,
}
}
fn config_write_error(code: ConfigWriteErrorCode, message: impl Into<String>) -> JSONRPCErrorError {
JSONRPCErrorError {
code: INVALID_REQUEST_ERROR_CODE,
message: message.into(),
data: Some(json!({
"config_write_error_code": code,
})),
}
}
#[cfg(test)]
mod tests {
use super::*;
use codex_protocol::protocol::AskForApproval as CoreAskForApproval;
use pretty_assertions::assert_eq;
#[test]
fn map_requirements_toml_to_api_converts_core_enums() {
let requirements = ConfigRequirementsToml {
allowed_approval_policies: Some(vec![
CoreAskForApproval::Never,
CoreAskForApproval::OnRequest,
]),
allowed_sandbox_modes: Some(vec![
CoreSandboxModeRequirement::ReadOnly,
CoreSandboxModeRequirement::ExternalSandbox,
]),
};
let mapped = map_requirements_toml_to_api(requirements);
assert_eq!(
mapped.allowed_approval_policies,
Some(vec![
codex_app_server_protocol::AskForApproval::Never,
codex_app_server_protocol::AskForApproval::OnRequest,
])
);
assert_eq!(
mapped.allowed_sandbox_modes,
Some(vec![SandboxMode::ReadOnly]),
);
}
}

View File

@@ -1,6 +1,5 @@
use std::num::NonZero;
use std::num::NonZeroUsize;
use std::path::Path;
use std::path::PathBuf;
use std::sync::Arc;
use std::sync::atomic::AtomicBool;
@@ -19,6 +18,10 @@ pub(crate) async fn run_fuzzy_file_search(
roots: Vec<String>,
cancellation_flag: Arc<AtomicBool>,
) -> Vec<FuzzyFileSearchResult> {
if roots.is_empty() {
return Vec::new();
}
#[expect(clippy::expect_used)]
let limit_per_root =
NonZero::new(LIMIT_PER_ROOT).expect("LIMIT_PER_ROOT should be a valid non-zero usize");
@@ -46,6 +49,7 @@ pub(crate) async fn run_fuzzy_file_search(
threads,
cancel_flag,
COMPUTE_INDICES,
true,
) {
Ok(res) => Ok((root, res)),
Err(err) => Err((root, err)),
@@ -58,11 +62,7 @@ pub(crate) async fn run_fuzzy_file_search(
Ok(Ok((root, res))) => {
for m in res.matches {
let path = m.path;
//TODO(shijie): Move file name generation to file_search lib.
let file_name = Path::new(&path)
.file_name()
.map(|name| name.to_string_lossy().into_owned())
.unwrap_or_else(|| path.clone());
let file_name = file_search::file_name_from_path(&path);
let result = FuzzyFileSearchResult {
root: root.clone(),
path,

View File

@@ -1,9 +1,8 @@
#![deny(clippy::print_stdout, clippy::print_stderr)]
use codex_common::CliConfigOverrides;
use codex_core::config::Config;
use codex_core::config::ConfigOverrides;
use opentelemetry_appender_tracing::layer::OpenTelemetryTracingBridge;
use codex_core::config::ConfigBuilder;
use codex_core::config_loader::LoaderOverrides;
use std::io::ErrorKind;
use std::io::Result as IoResult;
use std::path::PathBuf;
@@ -12,11 +11,13 @@ use crate::message_processor::MessageProcessor;
use crate::outgoing_message::OutgoingMessage;
use crate::outgoing_message::OutgoingMessageSender;
use codex_app_server_protocol::JSONRPCMessage;
use codex_feedback::CodexFeedback;
use tokio::io::AsyncBufReadExt;
use tokio::io::AsyncWriteExt;
use tokio::io::BufReader;
use tokio::io::{self};
use tokio::sync::mpsc;
use toml::Value as TomlValue;
use tracing::debug;
use tracing::error;
use tracing::info;
@@ -25,7 +26,9 @@ use tracing_subscriber::Layer;
use tracing_subscriber::layer::SubscriberExt;
use tracing_subscriber::util::SubscriberInitExt;
mod bespoke_event_handling;
mod codex_message_processor;
mod config_api;
mod error_code;
mod fuzzy_file_search;
mod message_processor;
@@ -40,10 +43,11 @@ const CHANNEL_CAPACITY: usize = 128;
pub async fn run_main(
codex_linux_sandbox_exe: Option<PathBuf>,
cli_config_overrides: CliConfigOverrides,
loader_overrides: LoaderOverrides,
) -> IoResult<()> {
// Set up channels.
let (incoming_tx, mut incoming_rx) = mpsc::channel::<JSONRPCMessage>(CHANNEL_CAPACITY);
let (outgoing_tx, mut outgoing_rx) = mpsc::unbounded_channel::<OutgoingMessage>();
let (outgoing_tx, mut outgoing_rx) = mpsc::channel::<OutgoingMessage>(CHANNEL_CAPACITY);
// Task: read from stdin, push to `incoming_tx`.
let stdin_reader_handle = tokio::spawn({
@@ -76,42 +80,65 @@ pub async fn run_main(
format!("error parsing -c overrides: {e}"),
)
})?;
let config = Config::load_with_cli_overrides(cli_kv_overrides, ConfigOverrides::default())
let loader_overrides_for_config_api = loader_overrides.clone();
let config = ConfigBuilder::default()
.cli_overrides(cli_kv_overrides.clone())
.loader_overrides(loader_overrides)
.build()
.await
.map_err(|e| {
std::io::Error::new(ErrorKind::InvalidData, format!("error loading config: {e}"))
})?;
let otel =
codex_core::otel_init::build_provider(&config, env!("CARGO_PKG_VERSION")).map_err(|e| {
std::io::Error::new(
ErrorKind::InvalidData,
format!("error loading otel config: {e}"),
)
})?;
let feedback = CodexFeedback::new();
let otel = codex_core::otel_init::build_provider(
&config,
env!("CARGO_PKG_VERSION"),
Some("codex_app_server"),
false,
)
.map_err(|e| {
std::io::Error::new(
ErrorKind::InvalidData,
format!("error loading otel config: {e}"),
)
})?;
// Install a simple subscriber so `tracing` output is visible. Users can
// control the log level with `RUST_LOG`.
let stderr_fmt = tracing_subscriber::fmt::layer()
.with_writer(std::io::stderr)
.with_span_events(tracing_subscriber::fmt::format::FmtSpan::FULL)
.with_filter(EnvFilter::from_default_env());
let feedback_layer = feedback.logger_layer();
let feedback_metadata_layer = feedback.metadata_layer();
let otel_logger_layer = otel.as_ref().and_then(|o| o.logger_layer());
let otel_tracing_layer = otel.as_ref().and_then(|o| o.tracing_layer());
let _ = tracing_subscriber::registry()
.with(stderr_fmt)
.with(otel.as_ref().map(|provider| {
OpenTelemetryTracingBridge::new(&provider.logger).with_filter(
tracing_subscriber::filter::filter_fn(codex_core::otel_init::codex_export_filter),
)
}))
.with(feedback_layer)
.with(feedback_metadata_layer)
.with(otel_logger_layer)
.with(otel_tracing_layer)
.try_init();
// Task: process incoming messages.
let processor_handle = tokio::spawn({
let outgoing_message_sender = OutgoingMessageSender::new(outgoing_tx);
let cli_overrides: Vec<(String, TomlValue)> = cli_kv_overrides.clone();
let loader_overrides = loader_overrides_for_config_api;
let mut processor = MessageProcessor::new(
outgoing_message_sender,
codex_linux_sandbox_exe,
std::sync::Arc::new(config),
cli_overrides,
loader_overrides,
feedback.clone(),
);
async move {
while let Some(msg) = incoming_rx.recv().await {

View File

@@ -1,10 +1,42 @@
use codex_app_server::run_main;
use codex_arg0::arg0_dispatch_or_else;
use codex_common::CliConfigOverrides;
use codex_core::config_loader::LoaderOverrides;
use std::path::PathBuf;
// Debug-only test hook: lets integration tests point the server at a temporary
// managed config file without writing to /etc.
const MANAGED_CONFIG_PATH_ENV_VAR: &str = "CODEX_APP_SERVER_MANAGED_CONFIG_PATH";
fn main() -> anyhow::Result<()> {
arg0_dispatch_or_else(|codex_linux_sandbox_exe| async move {
run_main(codex_linux_sandbox_exe, CliConfigOverrides::default()).await?;
let managed_config_path = managed_config_path_from_debug_env();
let loader_overrides = LoaderOverrides {
managed_config_path,
..Default::default()
};
run_main(
codex_linux_sandbox_exe,
CliConfigOverrides::default(),
loader_overrides,
)
.await?;
Ok(())
})
}
fn managed_config_path_from_debug_env() -> Option<PathBuf> {
#[cfg(debug_assertions)]
{
if let Ok(value) = std::env::var(MANAGED_CONFIG_PATH_ENV_VAR) {
return if value.is_empty() {
None
} else {
Some(PathBuf::from(value))
};
}
}
None
}

View File

@@ -1,28 +1,38 @@
use std::path::PathBuf;
use std::sync::Arc;
use crate::codex_message_processor::CodexMessageProcessor;
use crate::config_api::ConfigApi;
use crate::error_code::INVALID_REQUEST_ERROR_CODE;
use crate::outgoing_message::OutgoingMessageSender;
use codex_app_server_protocol::ClientInfo;
use codex_app_server_protocol::ClientRequest;
use codex_app_server_protocol::ConfigBatchWriteParams;
use codex_app_server_protocol::ConfigReadParams;
use codex_app_server_protocol::ConfigValueWriteParams;
use codex_app_server_protocol::InitializeResponse;
use codex_app_server_protocol::JSONRPCError;
use codex_app_server_protocol::JSONRPCErrorError;
use codex_app_server_protocol::JSONRPCNotification;
use codex_app_server_protocol::JSONRPCRequest;
use codex_app_server_protocol::JSONRPCResponse;
use codex_app_server_protocol::RequestId;
use codex_core::AuthManager;
use codex_core::ConversationManager;
use codex_core::ThreadManager;
use codex_core::config::Config;
use codex_core::config_loader::LoaderOverrides;
use codex_core::default_client::SetOriginatorError;
use codex_core::default_client::USER_AGENT_SUFFIX;
use codex_core::default_client::get_codex_user_agent;
use codex_core::default_client::set_default_originator;
use codex_feedback::CodexFeedback;
use codex_protocol::protocol::SessionSource;
use std::sync::Arc;
use toml::Value as TomlValue;
pub(crate) struct MessageProcessor {
outgoing: Arc<OutgoingMessageSender>,
codex_message_processor: CodexMessageProcessor,
config_api: ConfigApi,
initialized: bool,
}
@@ -33,87 +43,153 @@ impl MessageProcessor {
outgoing: OutgoingMessageSender,
codex_linux_sandbox_exe: Option<PathBuf>,
config: Arc<Config>,
cli_overrides: Vec<(String, TomlValue)>,
loader_overrides: LoaderOverrides,
feedback: CodexFeedback,
) -> Self {
let outgoing = Arc::new(outgoing);
let auth_manager = AuthManager::shared(config.codex_home.clone(), false);
let conversation_manager = Arc::new(ConversationManager::new(
let auth_manager = AuthManager::shared(
config.codex_home.clone(),
false,
config.cli_auth_credentials_store_mode,
);
let thread_manager = Arc::new(ThreadManager::new(
config.codex_home.clone(),
auth_manager.clone(),
SessionSource::VSCode,
));
let codex_message_processor = CodexMessageProcessor::new(
auth_manager,
conversation_manager,
thread_manager,
outgoing.clone(),
codex_linux_sandbox_exe,
config,
Arc::clone(&config),
cli_overrides.clone(),
feedback,
);
let config_api = ConfigApi::new(config.codex_home.clone(), cli_overrides, loader_overrides);
Self {
outgoing,
codex_message_processor,
config_api,
initialized: false,
}
}
pub(crate) async fn process_request(&mut self, request: JSONRPCRequest) {
let request_id = request.id.clone();
if let Ok(request_json) = serde_json::to_value(request)
&& let Ok(codex_request) = serde_json::from_value::<ClientRequest>(request_json)
{
match codex_request {
// Handle Initialize internally so CodexMessageProcessor does not have to concern
// itself with the `initialized` bool.
ClientRequest::Initialize { request_id, params } => {
if self.initialized {
let error = JSONRPCErrorError {
code: INVALID_REQUEST_ERROR_CODE,
message: "Already initialized".to_string(),
data: None,
};
self.outgoing.send_error(request_id, error).await;
return;
} else {
let ClientInfo {
name,
title: _title,
version,
} = params.client_info;
let user_agent_suffix = format!("{name}; {version}");
if let Ok(mut suffix) = USER_AGENT_SUFFIX.lock() {
*suffix = Some(user_agent_suffix);
let request_json = match serde_json::to_value(&request) {
Ok(request_json) => request_json,
Err(err) => {
let error = JSONRPCErrorError {
code: INVALID_REQUEST_ERROR_CODE,
message: format!("Invalid request: {err}"),
data: None,
};
self.outgoing.send_error(request_id, error).await;
return;
}
};
let codex_request = match serde_json::from_value::<ClientRequest>(request_json) {
Ok(codex_request) => codex_request,
Err(err) => {
let error = JSONRPCErrorError {
code: INVALID_REQUEST_ERROR_CODE,
message: format!("Invalid request: {err}"),
data: None,
};
self.outgoing.send_error(request_id, error).await;
return;
}
};
match codex_request {
// Handle Initialize internally so CodexMessageProcessor does not have to concern
// itself with the `initialized` bool.
ClientRequest::Initialize { request_id, params } => {
if self.initialized {
let error = JSONRPCErrorError {
code: INVALID_REQUEST_ERROR_CODE,
message: "Already initialized".to_string(),
data: None,
};
self.outgoing.send_error(request_id, error).await;
return;
} else {
let ClientInfo {
name,
title: _title,
version,
} = params.client_info;
if let Err(error) = set_default_originator(name.clone()) {
match error {
SetOriginatorError::InvalidHeaderValue => {
let error = JSONRPCErrorError {
code: INVALID_REQUEST_ERROR_CODE,
message: format!(
"Invalid clientInfo.name: '{name}'. Must be a valid HTTP header value."
),
data: None,
};
self.outgoing.send_error(request_id, error).await;
return;
}
SetOriginatorError::AlreadyInitialized => {
// No-op. This is expected to happen if the originator is already set via env var.
// TODO(owen): Once we remove support for CODEX_INTERNAL_ORIGINATOR_OVERRIDE,
// this will be an unexpected state and we can return a JSON-RPC error indicating
// internal server error.
}
}
let user_agent = get_codex_user_agent();
let response = InitializeResponse { user_agent };
self.outgoing.send_response(request_id, response).await;
self.initialized = true;
return;
}
}
_ => {
if !self.initialized {
let error = JSONRPCErrorError {
code: INVALID_REQUEST_ERROR_CODE,
message: "Not initialized".to_string(),
data: None,
};
self.outgoing.send_error(request_id, error).await;
return;
let user_agent_suffix = format!("{name}; {version}");
if let Ok(mut suffix) = USER_AGENT_SUFFIX.lock() {
*suffix = Some(user_agent_suffix);
}
let user_agent = get_codex_user_agent();
let response = InitializeResponse { user_agent };
self.outgoing.send_response(request_id, response).await;
self.initialized = true;
return;
}
}
_ => {
if !self.initialized {
let error = JSONRPCErrorError {
code: INVALID_REQUEST_ERROR_CODE,
message: "Not initialized".to_string(),
data: None,
};
self.outgoing.send_error(request_id, error).await;
return;
}
}
}
self.codex_message_processor
.process_request(codex_request)
.await;
} else {
let error = JSONRPCErrorError {
code: INVALID_REQUEST_ERROR_CODE,
message: "Invalid request".to_string(),
data: None,
};
self.outgoing.send_error(request_id, error).await;
match codex_request {
ClientRequest::ConfigRead { request_id, params } => {
self.handle_config_read(request_id, params).await;
}
ClientRequest::ConfigValueWrite { request_id, params } => {
self.handle_config_value_write(request_id, params).await;
}
ClientRequest::ConfigBatchWrite { request_id, params } => {
self.handle_config_batch_write(request_id, params).await;
}
ClientRequest::ConfigRequirementsRead {
request_id,
params: _,
} => {
self.handle_config_requirements_read(request_id).await;
}
other => {
self.codex_message_processor.process_request(other).await;
}
}
}
@@ -134,4 +210,40 @@ impl MessageProcessor {
pub(crate) fn process_error(&mut self, err: JSONRPCError) {
tracing::error!("<- error: {:?}", err);
}
async fn handle_config_read(&self, request_id: RequestId, params: ConfigReadParams) {
match self.config_api.read(params).await {
Ok(response) => self.outgoing.send_response(request_id, response).await,
Err(error) => self.outgoing.send_error(request_id, error).await,
}
}
async fn handle_config_value_write(
&self,
request_id: RequestId,
params: ConfigValueWriteParams,
) {
match self.config_api.write_value(params).await {
Ok(response) => self.outgoing.send_response(request_id, response).await,
Err(error) => self.outgoing.send_error(request_id, error).await,
}
}
async fn handle_config_batch_write(
&self,
request_id: RequestId,
params: ConfigBatchWriteParams,
) {
match self.config_api.batch_write(params).await {
Ok(response) => self.outgoing.send_response(request_id, response).await,
Err(error) => self.outgoing.send_error(request_id, error).await,
}
}
async fn handle_config_requirements_read(&self, request_id: RequestId) {
match self.config_api.config_requirements_read().await {
Ok(response) => self.outgoing.send_response(request_id, response).await,
Err(error) => self.outgoing.send_error(request_id, error).await,
}
}
}

View File

@@ -1,12 +1,18 @@
use std::sync::Arc;
use codex_app_server_protocol::Model;
use codex_app_server_protocol::ReasoningEffortOption;
use codex_common::model_presets::ModelPreset;
use codex_common::model_presets::ReasoningEffortPreset;
use codex_common::model_presets::builtin_model_presets;
use codex_core::ThreadManager;
use codex_core::config::Config;
use codex_protocol::openai_models::ModelPreset;
use codex_protocol::openai_models::ReasoningEffortPreset;
pub fn supported_models() -> Vec<Model> {
builtin_model_presets(None)
pub async fn supported_models(thread_manager: Arc<ThreadManager>, config: &Config) -> Vec<Model> {
thread_manager
.list_models(config)
.await
.into_iter()
.filter(|preset| preset.show_in_picker)
.map(model_from_preset)
.collect()
}
@@ -26,7 +32,7 @@ fn model_from_preset(preset: ModelPreset) -> Model {
}
fn reasoning_efforts_from_preset(
efforts: &'static [ReasoningEffortPreset],
efforts: Vec<ReasoningEffortPreset>,
) -> Vec<ReasoningEffortOption> {
efforts
.iter()

View File

@@ -16,15 +16,18 @@ use tracing::warn;
use crate::error_code::INTERNAL_ERROR_CODE;
#[cfg(test)]
use codex_protocol::account::PlanType;
/// Sends messages to the client and manages request callbacks.
pub(crate) struct OutgoingMessageSender {
next_request_id: AtomicI64,
sender: mpsc::UnboundedSender<OutgoingMessage>,
sender: mpsc::Sender<OutgoingMessage>,
request_id_to_callback: Mutex<HashMap<RequestId, oneshot::Sender<Result>>>,
}
impl OutgoingMessageSender {
pub(crate) fn new(sender: mpsc::UnboundedSender<OutgoingMessage>) -> Self {
pub(crate) fn new(sender: mpsc::Sender<OutgoingMessage>) -> Self {
Self {
next_request_id: AtomicI64::new(0),
sender,
@@ -45,8 +48,12 @@ impl OutgoingMessageSender {
}
let outgoing_message =
OutgoingMessage::Request(request.request_with_id(outgoing_message_id));
let _ = self.sender.send(outgoing_message);
OutgoingMessage::Request(request.request_with_id(outgoing_message_id.clone()));
if let Err(err) = self.sender.send(outgoing_message).await {
warn!("failed to send request {outgoing_message_id:?} to client: {err:?}");
let mut request_id_to_callback = self.request_id_to_callback.lock().await;
request_id_to_callback.remove(&outgoing_message_id);
}
rx_approve
}
@@ -72,7 +79,9 @@ impl OutgoingMessageSender {
match serde_json::to_value(response) {
Ok(result) => {
let outgoing_message = OutgoingMessage::Response(OutgoingResponse { id, result });
let _ = self.sender.send(outgoing_message);
if let Err(err) = self.sender.send(outgoing_message).await {
warn!("failed to send response to client: {err:?}");
}
}
Err(err) => {
self.send_error(
@@ -89,21 +98,29 @@ impl OutgoingMessageSender {
}
pub(crate) async fn send_server_notification(&self, notification: ServerNotification) {
let _ = self
if let Err(err) = self
.sender
.send(OutgoingMessage::AppServerNotification(notification));
.send(OutgoingMessage::AppServerNotification(notification))
.await
{
warn!("failed to send server notification to client: {err:?}");
}
}
/// All notifications should be migrated to [`ServerNotification`] and
/// [`OutgoingMessage::Notification`] should be removed.
pub(crate) async fn send_notification(&self, notification: OutgoingNotification) {
let outgoing_message = OutgoingMessage::Notification(notification);
let _ = self.sender.send(outgoing_message);
if let Err(err) = self.sender.send(outgoing_message).await {
warn!("failed to send notification to client: {err:?}");
}
}
pub(crate) async fn send_error(&self, id: RequestId, error: JSONRPCErrorError) {
let outgoing_message = OutgoingMessage::Error(OutgoingError { id, error });
let _ = self.sender.send(outgoing_message);
if let Err(err) = self.sender.send(outgoing_message).await {
warn!("failed to send error to client: {err:?}");
}
}
}
@@ -141,7 +158,13 @@ pub(crate) struct OutgoingError {
#[cfg(test)]
mod tests {
use codex_app_server_protocol::AccountLoginCompletedNotification;
use codex_app_server_protocol::AccountRateLimitsUpdatedNotification;
use codex_app_server_protocol::AccountUpdatedNotification;
use codex_app_server_protocol::AuthMode;
use codex_app_server_protocol::LoginChatGptCompleteNotification;
use codex_app_server_protocol::RateLimitSnapshot;
use codex_app_server_protocol::RateLimitWindow;
use pretty_assertions::assert_eq;
use serde_json::json;
use uuid::Uuid;
@@ -164,6 +187,7 @@ mod tests {
"params": {
"loginId": Uuid::nil(),
"success": true,
"error": null,
},
}),
serde_json::to_value(jsonrpc_notification)
@@ -171,4 +195,88 @@ mod tests {
"ensure the strum macros serialize the method field correctly"
);
}
#[test]
fn verify_account_login_completed_notification_serialization() {
let notification =
ServerNotification::AccountLoginCompleted(AccountLoginCompletedNotification {
login_id: Some(Uuid::nil().to_string()),
success: true,
error: None,
});
let jsonrpc_notification = OutgoingMessage::AppServerNotification(notification);
assert_eq!(
json!({
"method": "account/login/completed",
"params": {
"loginId": Uuid::nil().to_string(),
"success": true,
"error": null,
},
}),
serde_json::to_value(jsonrpc_notification)
.expect("ensure the notification serializes correctly"),
"ensure the notification serializes correctly"
);
}
#[test]
fn verify_account_rate_limits_notification_serialization() {
let notification =
ServerNotification::AccountRateLimitsUpdated(AccountRateLimitsUpdatedNotification {
rate_limits: RateLimitSnapshot {
primary: Some(RateLimitWindow {
used_percent: 25,
window_duration_mins: Some(15),
resets_at: Some(123),
}),
secondary: None,
credits: None,
plan_type: Some(PlanType::Plus),
},
});
let jsonrpc_notification = OutgoingMessage::AppServerNotification(notification);
assert_eq!(
json!({
"method": "account/rateLimits/updated",
"params": {
"rateLimits": {
"primary": {
"usedPercent": 25,
"windowDurationMins": 15,
"resetsAt": 123
},
"secondary": null,
"credits": null,
"planType": "plus"
}
},
}),
serde_json::to_value(jsonrpc_notification)
.expect("ensure the notification serializes correctly"),
"ensure the notification serializes correctly"
);
}
#[test]
fn verify_account_updated_notification_serialization() {
let notification = ServerNotification::AccountUpdated(AccountUpdatedNotification {
auth_mode: Some(AuthMode::ApiKey),
});
let jsonrpc_notification = OutgoingMessage::AppServerNotification(notification);
assert_eq!(
json!({
"method": "account/updated",
"params": {
"authMode": "apikey"
},
}),
serde_json::to_value(jsonrpc_notification)
.expect("ensure the notification serializes correctly"),
"ensure the notification serializes correctly"
);
}
}

View File

@@ -0,0 +1,7 @@
load("//:defs.bzl", "codex_rust_crate")
codex_rust_crate(
name = "common",
crate_name = "app_test_support",
crate_srcs = glob(["*.rs"]),
)

View File

@@ -1,18 +1,20 @@
[package]
edition = "2024"
name = "app_test_support"
version = { workspace = true }
version.workspace = true
edition.workspace = true
license.workspace = true
[lib]
path = "lib.rs"
[dependencies]
anyhow = { workspace = true }
assert_cmd = { workspace = true }
base64 = { workspace = true }
chrono = { workspace = true }
codex-app-server-protocol = { workspace = true }
codex-core = { workspace = true }
codex-core = { workspace = true, features = ["test-support"] }
codex-protocol = { workspace = true }
codex-utils-cargo-bin = { workspace = true }
serde = { workspace = true }
serde_json = { workspace = true }
tokio = { workspace = true, features = [
@@ -21,4 +23,7 @@ tokio = { workspace = true, features = [
"process",
"rt-multi-thread",
] }
uuid = { workspace = true }
wiremock = { workspace = true }
core_test_support = { path = "../../../core/tests/common" }
shlex = { workspace = true }

View File

@@ -6,9 +6,9 @@ use base64::Engine;
use base64::engine::general_purpose::URL_SAFE_NO_PAD;
use chrono::DateTime;
use chrono::Utc;
use codex_core::auth::AuthCredentialsStoreMode;
use codex_core::auth::AuthDotJson;
use codex_core::auth::get_auth_file;
use codex_core::auth::write_auth_json;
use codex_core::auth::save_auth;
use codex_core::token_data::TokenData;
use codex_core::token_data::parse_id_token;
use serde_json::json;
@@ -109,7 +109,11 @@ pub fn encode_id_token(claims: &ChatGptIdTokenClaims) -> Result<String> {
Ok(format!("{header_b64}.{payload_b64}.{signature_b64}"))
}
pub fn write_chatgpt_auth(codex_home: &Path, fixture: ChatGptAuthFixture) -> Result<()> {
pub fn write_chatgpt_auth(
codex_home: &Path,
fixture: ChatGptAuthFixture,
cli_auth_credentials_store_mode: AuthCredentialsStoreMode,
) -> Result<()> {
let id_token_raw = encode_id_token(&fixture.claims)?;
let id_token = parse_id_token(&id_token_raw).context("parse id token")?;
let tokens = TokenData {
@@ -127,5 +131,5 @@ pub fn write_chatgpt_auth(codex_home: &Path, fixture: ChatGptAuthFixture) -> Res
last_refresh,
};
write_auth_json(&get_auth_file(codex_home), &auth).context("write auth.json")
save_auth(codex_home, &auth, cli_auth_credentials_store_mode).context("write auth.json")
}

View File

@@ -1,18 +1,34 @@
mod auth_fixtures;
mod mcp_process;
mod mock_model_server;
mod models_cache;
mod responses;
mod rollout;
pub use auth_fixtures::ChatGptAuthFixture;
pub use auth_fixtures::ChatGptIdTokenClaims;
pub use auth_fixtures::encode_id_token;
pub use auth_fixtures::write_chatgpt_auth;
use codex_app_server_protocol::JSONRPCResponse;
pub use core_test_support::format_with_current_shell;
pub use core_test_support::format_with_current_shell_display;
pub use core_test_support::format_with_current_shell_display_non_login;
pub use core_test_support::format_with_current_shell_non_login;
pub use core_test_support::test_path_buf_with_windows;
pub use core_test_support::test_tmp_path;
pub use core_test_support::test_tmp_path_buf;
pub use mcp_process::DEFAULT_CLIENT_NAME;
pub use mcp_process::McpProcess;
pub use mock_model_server::create_mock_chat_completions_server;
pub use mock_model_server::create_mock_responses_server_repeating_assistant;
pub use mock_model_server::create_mock_responses_server_sequence;
pub use mock_model_server::create_mock_responses_server_sequence_unchecked;
pub use models_cache::write_models_cache;
pub use models_cache::write_models_cache_with_models;
pub use responses::create_apply_patch_sse_response;
pub use responses::create_exec_command_sse_response;
pub use responses::create_final_assistant_message_sse_response;
pub use responses::create_shell_sse_response;
pub use responses::create_shell_command_sse_response;
pub use rollout::create_fake_rollout;
use serde::de::DeserializeOwned;
pub fn to_response<T: DeserializeOwned>(response: JSONRPCResponse) -> anyhow::Result<T> {

View File

@@ -11,33 +11,47 @@ use tokio::process::ChildStdin;
use tokio::process::ChildStdout;
use anyhow::Context;
use assert_cmd::prelude::*;
use codex_app_server_protocol::AddConversationListenerParams;
use codex_app_server_protocol::ArchiveConversationParams;
use codex_app_server_protocol::CancelLoginAccountParams;
use codex_app_server_protocol::CancelLoginChatGptParams;
use codex_app_server_protocol::ClientInfo;
use codex_app_server_protocol::ClientNotification;
use codex_app_server_protocol::ConfigBatchWriteParams;
use codex_app_server_protocol::ConfigReadParams;
use codex_app_server_protocol::ConfigValueWriteParams;
use codex_app_server_protocol::FeedbackUploadParams;
use codex_app_server_protocol::ForkConversationParams;
use codex_app_server_protocol::GetAccountParams;
use codex_app_server_protocol::GetAuthStatusParams;
use codex_app_server_protocol::InitializeParams;
use codex_app_server_protocol::InterruptConversationParams;
use codex_app_server_protocol::ListConversationsParams;
use codex_app_server_protocol::ListModelsParams;
use codex_app_server_protocol::LoginApiKeyParams;
use codex_app_server_protocol::NewConversationParams;
use codex_app_server_protocol::RemoveConversationListenerParams;
use codex_app_server_protocol::ResumeConversationParams;
use codex_app_server_protocol::SendUserMessageParams;
use codex_app_server_protocol::SendUserTurnParams;
use codex_app_server_protocol::ServerRequest;
use codex_app_server_protocol::SetDefaultModelParams;
use codex_app_server_protocol::JSONRPCError;
use codex_app_server_protocol::JSONRPCMessage;
use codex_app_server_protocol::JSONRPCNotification;
use codex_app_server_protocol::JSONRPCRequest;
use codex_app_server_protocol::JSONRPCResponse;
use codex_app_server_protocol::ListConversationsParams;
use codex_app_server_protocol::LoginApiKeyParams;
use codex_app_server_protocol::ModelListParams;
use codex_app_server_protocol::NewConversationParams;
use codex_app_server_protocol::RemoveConversationListenerParams;
use codex_app_server_protocol::RequestId;
use std::process::Command as StdCommand;
use codex_app_server_protocol::ResumeConversationParams;
use codex_app_server_protocol::ReviewStartParams;
use codex_app_server_protocol::SendUserMessageParams;
use codex_app_server_protocol::SendUserTurnParams;
use codex_app_server_protocol::ServerRequest;
use codex_app_server_protocol::SetDefaultModelParams;
use codex_app_server_protocol::ThreadArchiveParams;
use codex_app_server_protocol::ThreadForkParams;
use codex_app_server_protocol::ThreadListParams;
use codex_app_server_protocol::ThreadLoadedListParams;
use codex_app_server_protocol::ThreadResumeParams;
use codex_app_server_protocol::ThreadRollbackParams;
use codex_app_server_protocol::ThreadStartParams;
use codex_app_server_protocol::TurnInterruptParams;
use codex_app_server_protocol::TurnStartParams;
use tokio::process::Command;
pub struct McpProcess {
@@ -49,9 +63,11 @@ pub struct McpProcess {
process: Child,
stdin: ChildStdin,
stdout: BufReader<ChildStdout>,
pending_user_messages: VecDeque<JSONRPCNotification>,
pending_messages: VecDeque<JSONRPCMessage>,
}
pub const DEFAULT_CLIENT_NAME: &str = "codex-app-server-tests";
impl McpProcess {
pub async fn new(codex_home: &Path) -> anyhow::Result<Self> {
Self::new_with_env(codex_home, &[]).await
@@ -66,12 +82,8 @@ impl McpProcess {
codex_home: &Path,
env_overrides: &[(&str, Option<&str>)],
) -> anyhow::Result<Self> {
// Use assert_cmd to locate the binary path and then switch to tokio::process::Command
let std_cmd = StdCommand::cargo_bin("codex-app-server")
.context("should find binary for codex-mcp-server")?;
let program = std_cmd.get_program().to_owned();
let program = codex_utils_cargo_bin::cargo_bin("codex-app-server")
.context("should find binary for codex-app-server")?;
let mut cmd = Command::new(program);
cmd.stdin(Stdio::piped());
@@ -120,39 +132,68 @@ impl McpProcess {
process,
stdin,
stdout,
pending_user_messages: VecDeque::new(),
pending_messages: VecDeque::new(),
})
}
/// Performs the initialization handshake with the MCP server.
pub async fn initialize(&mut self) -> anyhow::Result<()> {
let params = Some(serde_json::to_value(InitializeParams {
client_info: ClientInfo {
name: "codex-app-server-tests".to_string(),
let initialized = self
.initialize_with_client_info(ClientInfo {
name: DEFAULT_CLIENT_NAME.to_string(),
title: None,
version: "0.1.0".to_string(),
},
})?);
let req_id = self.send_request("initialize", params).await?;
let initialized = self.read_jsonrpc_message().await?;
let JSONRPCMessage::Response(response) = initialized else {
})
.await?;
let JSONRPCMessage::Response(_) = initialized else {
unreachable!("expected JSONRPCMessage::Response for initialize, got {initialized:?}");
};
if response.id != RequestId::Integer(req_id) {
anyhow::bail!(
"initialize response id mismatch: expected {}, got {:?}",
req_id,
response.id
);
}
// Send notifications/initialized to ack the response.
self.send_notification(ClientNotification::Initialized)
.await?;
Ok(())
}
/// Sends initialize with the provided client info and returns the response/error message.
pub async fn initialize_with_client_info(
&mut self,
client_info: ClientInfo,
) -> anyhow::Result<JSONRPCMessage> {
let params = Some(serde_json::to_value(InitializeParams { client_info })?);
let request_id = self.send_request("initialize", params).await?;
let message = self.read_jsonrpc_message().await?;
match message {
JSONRPCMessage::Response(response) => {
if response.id != RequestId::Integer(request_id) {
anyhow::bail!(
"initialize response id mismatch: expected {}, got {:?}",
request_id,
response.id
);
}
// Send notifications/initialized to ack the response.
self.send_notification(ClientNotification::Initialized)
.await?;
Ok(JSONRPCMessage::Response(response))
}
JSONRPCMessage::Error(error) => {
if error.id != RequestId::Integer(request_id) {
anyhow::bail!(
"initialize error id mismatch: expected {}, got {:?}",
request_id,
error.id
);
}
Ok(JSONRPCMessage::Error(error))
}
JSONRPCMessage::Notification(notification) => {
anyhow::bail!("unexpected JSONRPCMessage::Notification: {notification:?}");
}
JSONRPCMessage::Request(request) => {
anyhow::bail!("unexpected JSONRPCMessage::Request: {request:?}");
}
}
}
/// Send a `newConversation` JSON-RPC request.
pub async fn send_new_conversation_request(
&mut self,
@@ -191,7 +232,7 @@ impl McpProcess {
}
/// Send a `removeConversationListener` JSON-RPC request.
pub async fn send_remove_conversation_listener_request(
pub async fn send_remove_thread_listener_request(
&mut self,
params: RemoveConversationListenerParams,
) -> anyhow::Result<i64> {
@@ -242,6 +283,24 @@ impl McpProcess {
self.send_request("account/rateLimits/read", None).await
}
/// Send an `account/read` JSON-RPC request.
pub async fn send_get_account_request(
&mut self,
params: GetAccountParams,
) -> anyhow::Result<i64> {
let params = Some(serde_json::to_value(params)?);
self.send_request("account/read", params).await
}
/// Send a `feedback/upload` JSON-RPC request.
pub async fn send_feedback_upload_request(
&mut self,
params: FeedbackUploadParams,
) -> anyhow::Result<i64> {
let params = Some(serde_json::to_value(params)?);
self.send_request("feedback/upload", params).await
}
/// Send a `userInfo` JSON-RPC request.
pub async fn send_user_info_request(&mut self) -> anyhow::Result<i64> {
self.send_request("userInfo", None).await
@@ -265,10 +324,73 @@ impl McpProcess {
self.send_request("listConversations", params).await
}
/// Send a `thread/start` JSON-RPC request.
pub async fn send_thread_start_request(
&mut self,
params: ThreadStartParams,
) -> anyhow::Result<i64> {
let params = Some(serde_json::to_value(params)?);
self.send_request("thread/start", params).await
}
/// Send a `thread/resume` JSON-RPC request.
pub async fn send_thread_resume_request(
&mut self,
params: ThreadResumeParams,
) -> anyhow::Result<i64> {
let params = Some(serde_json::to_value(params)?);
self.send_request("thread/resume", params).await
}
/// Send a `thread/fork` JSON-RPC request.
pub async fn send_thread_fork_request(
&mut self,
params: ThreadForkParams,
) -> anyhow::Result<i64> {
let params = Some(serde_json::to_value(params)?);
self.send_request("thread/fork", params).await
}
/// Send a `thread/archive` JSON-RPC request.
pub async fn send_thread_archive_request(
&mut self,
params: ThreadArchiveParams,
) -> anyhow::Result<i64> {
let params = Some(serde_json::to_value(params)?);
self.send_request("thread/archive", params).await
}
/// Send a `thread/rollback` JSON-RPC request.
pub async fn send_thread_rollback_request(
&mut self,
params: ThreadRollbackParams,
) -> anyhow::Result<i64> {
let params = Some(serde_json::to_value(params)?);
self.send_request("thread/rollback", params).await
}
/// Send a `thread/list` JSON-RPC request.
pub async fn send_thread_list_request(
&mut self,
params: ThreadListParams,
) -> anyhow::Result<i64> {
let params = Some(serde_json::to_value(params)?);
self.send_request("thread/list", params).await
}
/// Send a `thread/loaded/list` JSON-RPC request.
pub async fn send_thread_loaded_list_request(
&mut self,
params: ThreadLoadedListParams,
) -> anyhow::Result<i64> {
let params = Some(serde_json::to_value(params)?);
self.send_request("thread/loaded/list", params).await
}
/// Send a `model/list` JSON-RPC request.
pub async fn send_list_models_request(
&mut self,
params: ListModelsParams,
params: ModelListParams,
) -> anyhow::Result<i64> {
let params = Some(serde_json::to_value(params)?);
self.send_request("model/list", params).await
@@ -283,6 +405,15 @@ impl McpProcess {
self.send_request("resumeConversation", params).await
}
/// Send a `forkConversation` JSON-RPC request.
pub async fn send_fork_conversation_request(
&mut self,
params: ForkConversationParams,
) -> anyhow::Result<i64> {
let params = Some(serde_json::to_value(params)?);
self.send_request("forkConversation", params).await
}
/// Send a `loginApiKey` JSON-RPC request.
pub async fn send_login_api_key_request(
&mut self,
@@ -297,6 +428,33 @@ impl McpProcess {
self.send_request("loginChatGpt", None).await
}
/// Send a `turn/start` JSON-RPC request (v2).
pub async fn send_turn_start_request(
&mut self,
params: TurnStartParams,
) -> anyhow::Result<i64> {
let params = Some(serde_json::to_value(params)?);
self.send_request("turn/start", params).await
}
/// Send a `turn/interrupt` JSON-RPC request (v2).
pub async fn send_turn_interrupt_request(
&mut self,
params: TurnInterruptParams,
) -> anyhow::Result<i64> {
let params = Some(serde_json::to_value(params)?);
self.send_request("turn/interrupt", params).await
}
/// Send a `review/start` JSON-RPC request (v2).
pub async fn send_review_start_request(
&mut self,
params: ReviewStartParams,
) -> anyhow::Result<i64> {
let params = Some(serde_json::to_value(params)?);
self.send_request("review/start", params).await
}
/// Send a `cancelLoginChatGpt` JSON-RPC request.
pub async fn send_cancel_login_chat_gpt_request(
&mut self,
@@ -311,6 +469,64 @@ impl McpProcess {
self.send_request("logoutChatGpt", None).await
}
pub async fn send_config_read_request(
&mut self,
params: ConfigReadParams,
) -> anyhow::Result<i64> {
let params = Some(serde_json::to_value(params)?);
self.send_request("config/read", params).await
}
pub async fn send_config_value_write_request(
&mut self,
params: ConfigValueWriteParams,
) -> anyhow::Result<i64> {
let params = Some(serde_json::to_value(params)?);
self.send_request("config/value/write", params).await
}
pub async fn send_config_batch_write_request(
&mut self,
params: ConfigBatchWriteParams,
) -> anyhow::Result<i64> {
let params = Some(serde_json::to_value(params)?);
self.send_request("config/batchWrite", params).await
}
/// Send an `account/logout` JSON-RPC request.
pub async fn send_logout_account_request(&mut self) -> anyhow::Result<i64> {
self.send_request("account/logout", None).await
}
/// Send an `account/login/start` JSON-RPC request for API key login.
pub async fn send_login_account_api_key_request(
&mut self,
api_key: &str,
) -> anyhow::Result<i64> {
let params = serde_json::json!({
"type": "apiKey",
"apiKey": api_key,
});
self.send_request("account/login/start", Some(params)).await
}
/// Send an `account/login/start` JSON-RPC request for ChatGPT login.
pub async fn send_login_account_chatgpt_request(&mut self) -> anyhow::Result<i64> {
let params = serde_json::json!({
"type": "chatgpt"
});
self.send_request("account/login/start", Some(params)).await
}
/// Send an `account/login/cancel` JSON-RPC request.
pub async fn send_cancel_login_account_request(
&mut self,
params: CancelLoginAccountParams,
) -> anyhow::Result<i64> {
let params = Some(serde_json::to_value(params)?);
self.send_request("account/login/cancel", params).await
}
/// Send a `fuzzyFileSearch` JSON-RPC request.
pub async fn send_fuzzy_file_search_request(
&mut self,
@@ -389,27 +605,16 @@ impl McpProcess {
pub async fn read_stream_until_request_message(&mut self) -> anyhow::Result<ServerRequest> {
eprintln!("in read_stream_until_request_message()");
loop {
let message = self.read_jsonrpc_message().await?;
let message = self
.read_stream_until_message(|message| matches!(message, JSONRPCMessage::Request(_)))
.await?;
match message {
JSONRPCMessage::Notification(notification) => {
eprintln!("notification: {notification:?}");
self.enqueue_user_message(notification);
}
JSONRPCMessage::Request(jsonrpc_request) => {
return jsonrpc_request.try_into().with_context(
|| "failed to deserialize ServerRequest from JSONRPCRequest",
);
}
JSONRPCMessage::Error(_) => {
anyhow::bail!("unexpected JSONRPCMessage::Error: {message:?}");
}
JSONRPCMessage::Response(_) => {
anyhow::bail!("unexpected JSONRPCMessage::Response: {message:?}");
}
}
}
let JSONRPCMessage::Request(jsonrpc_request) = message else {
unreachable!("expected JSONRPCMessage::Request, got {message:?}");
};
jsonrpc_request
.try_into()
.with_context(|| "failed to deserialize ServerRequest from JSONRPCRequest")
}
pub async fn read_stream_until_response_message(
@@ -418,52 +623,32 @@ impl McpProcess {
) -> anyhow::Result<JSONRPCResponse> {
eprintln!("in read_stream_until_response_message({request_id:?})");
loop {
let message = self.read_jsonrpc_message().await?;
match message {
JSONRPCMessage::Notification(notification) => {
eprintln!("notification: {notification:?}");
self.enqueue_user_message(notification);
}
JSONRPCMessage::Request(_) => {
anyhow::bail!("unexpected JSONRPCMessage::Request: {message:?}");
}
JSONRPCMessage::Error(_) => {
anyhow::bail!("unexpected JSONRPCMessage::Error: {message:?}");
}
JSONRPCMessage::Response(jsonrpc_response) => {
if jsonrpc_response.id == request_id {
return Ok(jsonrpc_response);
}
}
}
}
let message = self
.read_stream_until_message(|message| {
Self::message_request_id(message) == Some(&request_id)
})
.await?;
let JSONRPCMessage::Response(response) = message else {
unreachable!("expected JSONRPCMessage::Response, got {message:?}");
};
Ok(response)
}
pub async fn read_stream_until_error_message(
&mut self,
request_id: RequestId,
) -> anyhow::Result<JSONRPCError> {
loop {
let message = self.read_jsonrpc_message().await?;
match message {
JSONRPCMessage::Notification(notification) => {
eprintln!("notification: {notification:?}");
self.enqueue_user_message(notification);
}
JSONRPCMessage::Request(_) => {
anyhow::bail!("unexpected JSONRPCMessage::Request: {message:?}");
}
JSONRPCMessage::Response(_) => {
// Keep scanning; we're waiting for an error with matching id.
}
JSONRPCMessage::Error(err) => {
if err.id == request_id {
return Ok(err);
}
}
}
}
let message = self
.read_stream_until_message(|message| {
Self::message_request_id(message) == Some(&request_id)
})
.await?;
let JSONRPCMessage::Error(err) = message else {
unreachable!("expected JSONRPCMessage::Error, got {message:?}");
};
Ok(err)
}
pub async fn read_stream_until_notification_message(
@@ -472,46 +657,64 @@ impl McpProcess {
) -> anyhow::Result<JSONRPCNotification> {
eprintln!("in read_stream_until_notification_message({method})");
if let Some(notification) = self.take_pending_notification_by_method(method) {
return Ok(notification);
let message = self
.read_stream_until_message(|message| {
matches!(
message,
JSONRPCMessage::Notification(notification) if notification.method == method
)
})
.await?;
let JSONRPCMessage::Notification(notification) = message else {
unreachable!("expected JSONRPCMessage::Notification, got {message:?}");
};
Ok(notification)
}
/// Clears any buffered messages so future reads only consider new stream items.
///
/// We call this when e.g. we want to validate against the next turn and no longer care about
/// messages buffered from the prior turn.
pub fn clear_message_buffer(&mut self) {
self.pending_messages.clear();
}
/// Reads the stream until a message matches `predicate`, buffering any non-matching messages
/// for later reads.
async fn read_stream_until_message<F>(&mut self, predicate: F) -> anyhow::Result<JSONRPCMessage>
where
F: Fn(&JSONRPCMessage) -> bool,
{
if let Some(message) = self.take_pending_message(&predicate) {
return Ok(message);
}
loop {
let message = self.read_jsonrpc_message().await?;
match message {
JSONRPCMessage::Notification(notification) => {
if notification.method == method {
return Ok(notification);
}
self.enqueue_user_message(notification);
}
JSONRPCMessage::Request(_) => {
anyhow::bail!("unexpected JSONRPCMessage::Request: {message:?}");
}
JSONRPCMessage::Error(_) => {
anyhow::bail!("unexpected JSONRPCMessage::Error: {message:?}");
}
JSONRPCMessage::Response(_) => {
anyhow::bail!("unexpected JSONRPCMessage::Response: {message:?}");
}
if predicate(&message) {
return Ok(message);
}
self.pending_messages.push_back(message);
}
}
fn take_pending_notification_by_method(&mut self, method: &str) -> Option<JSONRPCNotification> {
if let Some(pos) = self
.pending_user_messages
.iter()
.position(|notification| notification.method == method)
{
return self.pending_user_messages.remove(pos);
fn take_pending_message<F>(&mut self, predicate: &F) -> Option<JSONRPCMessage>
where
F: Fn(&JSONRPCMessage) -> bool,
{
if let Some(pos) = self.pending_messages.iter().position(predicate) {
return self.pending_messages.remove(pos);
}
None
}
fn enqueue_user_message(&mut self, notification: JSONRPCNotification) {
if notification.method == "codex/event/user_message" {
self.pending_user_messages.push_back(notification);
fn message_request_id(message: &JSONRPCMessage) -> Option<&RequestId> {
match message {
JSONRPCMessage::Request(request) => Some(&request.id),
JSONRPCMessage::Response(response) => Some(&response.id),
JSONRPCMessage::Error(err) => Some(&err.id),
JSONRPCMessage::Notification(_) => None,
}
}
}

View File

@@ -1,17 +1,18 @@
use std::sync::atomic::AtomicUsize;
use std::sync::atomic::Ordering;
use core_test_support::responses;
use wiremock::Mock;
use wiremock::MockServer;
use wiremock::Respond;
use wiremock::ResponseTemplate;
use wiremock::matchers::method;
use wiremock::matchers::path;
use wiremock::matchers::path_regex;
/// Create a mock server that will provide the responses, in order, for
/// requests to the `/v1/chat/completions` endpoint.
pub async fn create_mock_chat_completions_server(responses: Vec<String>) -> MockServer {
let server = MockServer::start().await;
/// requests to the `/v1/responses` endpoint.
pub async fn create_mock_responses_server_sequence(responses: Vec<String>) -> MockServer {
let server = responses::start_mock_server().await;
let num_calls = responses.len();
let seq_responder = SeqResponder {
@@ -20,7 +21,7 @@ pub async fn create_mock_chat_completions_server(responses: Vec<String>) -> Mock
};
Mock::given(method("POST"))
.and(path("/v1/chat/completions"))
.and(path_regex(".*/responses$"))
.respond_with(seq_responder)
.expect(num_calls as u64)
.mount(&server)
@@ -29,6 +30,25 @@ pub async fn create_mock_chat_completions_server(responses: Vec<String>) -> Mock
server
}
/// Same as `create_mock_responses_server_sequence` but does not enforce an
/// expectation on the number of calls.
pub async fn create_mock_responses_server_sequence_unchecked(responses: Vec<String>) -> MockServer {
let server = responses::start_mock_server().await;
let seq_responder = SeqResponder {
num_calls: AtomicUsize::new(0),
responses,
};
Mock::given(method("POST"))
.and(path_regex(".*/responses$"))
.respond_with(seq_responder)
.mount(&server)
.await;
server
}
struct SeqResponder {
num_calls: AtomicUsize,
responses: Vec<String>,
@@ -38,10 +58,24 @@ impl Respond for SeqResponder {
fn respond(&self, _: &wiremock::Request) -> ResponseTemplate {
let call_num = self.num_calls.fetch_add(1, Ordering::SeqCst);
match self.responses.get(call_num) {
Some(response) => ResponseTemplate::new(200)
.insert_header("content-type", "text/event-stream")
.set_body_raw(response.clone(), "text/event-stream"),
Some(response) => responses::sse_response(response.clone()),
None => panic!("no response for {call_num}"),
}
}
}
/// Create a mock responses API server that returns the same assistant message for every request.
pub async fn create_mock_responses_server_repeating_assistant(message: &str) -> MockServer {
let server = responses::start_mock_server().await;
let body = responses::sse(vec![
responses::ev_response_created("resp-1"),
responses::ev_assistant_message("msg-1", message),
responses::ev_completed("resp-1"),
]);
Mock::given(method("POST"))
.and(path_regex(".*/responses$"))
.respond_with(responses::sse_response(body))
.mount(&server)
.await;
server
}

View File

@@ -0,0 +1,83 @@
use chrono::DateTime;
use chrono::Utc;
use codex_core::models_manager::model_presets::all_model_presets;
use codex_protocol::openai_models::ConfigShellToolType;
use codex_protocol::openai_models::ModelInfo;
use codex_protocol::openai_models::ModelPreset;
use codex_protocol::openai_models::ModelVisibility;
use codex_protocol::openai_models::TruncationPolicyConfig;
use serde_json::json;
use std::path::Path;
/// Convert a ModelPreset to ModelInfo for cache storage.
fn preset_to_info(preset: &ModelPreset, priority: i32) -> ModelInfo {
ModelInfo {
slug: preset.id.clone(),
display_name: preset.display_name.clone(),
description: Some(preset.description.clone()),
default_reasoning_level: Some(preset.default_reasoning_effort),
supported_reasoning_levels: preset.supported_reasoning_efforts.clone(),
shell_type: ConfigShellToolType::ShellCommand,
visibility: if preset.show_in_picker {
ModelVisibility::List
} else {
ModelVisibility::Hide
},
supported_in_api: true,
priority,
upgrade: preset.upgrade.as_ref().map(|u| u.id.clone()),
base_instructions: "base instructions".to_string(),
supports_reasoning_summaries: false,
support_verbosity: false,
default_verbosity: None,
apply_patch_tool_type: None,
truncation_policy: TruncationPolicyConfig::bytes(10_000),
supports_parallel_tool_calls: false,
context_window: Some(272_000),
auto_compact_token_limit: None,
effective_context_window_percent: 95,
experimental_supported_tools: Vec::new(),
}
}
/// Write a models_cache.json file to the codex home directory.
/// This prevents ModelsManager from making network requests to refresh models.
/// The cache will be treated as fresh (within TTL) and used instead of fetching from the network.
/// Uses the built-in model presets from ModelsManager, converted to ModelInfo format.
pub fn write_models_cache(codex_home: &Path) -> std::io::Result<()> {
// Get all presets and filter for show_in_picker (same as builtin_model_presets does)
let presets: Vec<&ModelPreset> = all_model_presets()
.iter()
.filter(|preset| preset.show_in_picker)
.collect();
// Convert presets to ModelInfo, assigning priorities (lower = earlier in list).
// Priority is used for sorting, so the first model gets the lowest priority.
let models: Vec<ModelInfo> = presets
.iter()
.enumerate()
.map(|(idx, preset)| {
// Lower priority = earlier in list.
let priority = idx as i32;
preset_to_info(preset, priority)
})
.collect();
write_models_cache_with_models(codex_home, models)
}
/// Write a models_cache.json file with specific models.
/// Useful when tests need specific models to be available.
pub fn write_models_cache_with_models(
codex_home: &Path,
models: Vec<ModelInfo>,
) -> std::io::Result<()> {
let cache_path = codex_home.join("models_cache.json");
// DateTime<Utc> serializes to RFC3339 format by default with serde
let fetched_at: DateTime<Utc> = Utc::now();
let cache = json!({
"fetched_at": fetched_at,
"etag": null,
"models": models
});
std::fs::write(cache_path, serde_json::to_string_pretty(&cache)?)
}

View File

@@ -1,95 +1,62 @@
use core_test_support::responses;
use serde_json::json;
use std::path::Path;
pub fn create_shell_sse_response(
pub fn create_shell_command_sse_response(
command: Vec<String>,
workdir: Option<&Path>,
timeout_ms: Option<u64>,
call_id: &str,
) -> anyhow::Result<String> {
// The `arguments`` for the `shell` tool is a serialized JSON object.
// The `arguments` for the `shell_command` tool is a serialized JSON object.
let command_str = shlex::try_join(command.iter().map(String::as_str))?;
let tool_call_arguments = serde_json::to_string(&json!({
"command": command,
"command": command_str,
"workdir": workdir.map(|w| w.to_string_lossy()),
"timeout": timeout_ms
"timeout_ms": timeout_ms
}))?;
let tool_call = json!({
"choices": [
{
"delta": {
"tool_calls": [
{
"id": call_id,
"function": {
"name": "shell",
"arguments": tool_call_arguments
}
}
]
},
"finish_reason": "tool_calls"
}
]
});
let sse = format!(
"data: {}\n\ndata: DONE\n\n",
serde_json::to_string(&tool_call)?
);
Ok(sse)
Ok(responses::sse(vec![
responses::ev_response_created("resp-1"),
responses::ev_function_call(call_id, "shell_command", &tool_call_arguments),
responses::ev_completed("resp-1"),
]))
}
pub fn create_final_assistant_message_sse_response(message: &str) -> anyhow::Result<String> {
let assistant_message = json!({
"choices": [
{
"delta": {
"content": message
},
"finish_reason": "stop"
}
]
});
let sse = format!(
"data: {}\n\ndata: DONE\n\n",
serde_json::to_string(&assistant_message)?
);
Ok(sse)
Ok(responses::sse(vec![
responses::ev_response_created("resp-1"),
responses::ev_assistant_message("msg-1", message),
responses::ev_completed("resp-1"),
]))
}
pub fn create_apply_patch_sse_response(
patch_content: &str,
call_id: &str,
) -> anyhow::Result<String> {
// Use shell command to call apply_patch with heredoc format
let shell_command = format!("apply_patch <<'EOF'\n{patch_content}\nEOF");
let tool_call_arguments = serde_json::to_string(&json!({
"command": ["bash", "-lc", shell_command]
}))?;
let tool_call = json!({
"choices": [
{
"delta": {
"tool_calls": [
{
"id": call_id,
"function": {
"name": "shell",
"arguments": tool_call_arguments
}
}
]
},
"finish_reason": "tool_calls"
}
]
});
let sse = format!(
"data: {}\n\ndata: DONE\n\n",
serde_json::to_string(&tool_call)?
);
Ok(sse)
Ok(responses::sse(vec![
responses::ev_response_created("resp-1"),
responses::ev_apply_patch_shell_command_call_via_heredoc(call_id, patch_content),
responses::ev_completed("resp-1"),
]))
}
pub fn create_exec_command_sse_response(call_id: &str) -> anyhow::Result<String> {
let (cmd, args) = if cfg!(windows) {
("cmd.exe", vec!["/d", "/c", "echo hi"])
} else {
("/bin/sh", vec!["-c", "echo hi"])
};
let command = std::iter::once(cmd.to_string())
.chain(args.into_iter().map(str::to_string))
.collect::<Vec<_>>();
let tool_call_arguments = serde_json::to_string(&json!({
"cmd": command.join(" "),
"yield_time_ms": 500
}))?;
Ok(responses::sse(vec![
responses::ev_response_created("resp-1"),
responses::ev_function_call(call_id, "exec_command", &tool_call_arguments),
responses::ev_completed("resp-1"),
]))
}

View File

@@ -0,0 +1,89 @@
use anyhow::Result;
use codex_protocol::ThreadId;
use codex_protocol::protocol::GitInfo;
use codex_protocol::protocol::SessionMeta;
use codex_protocol::protocol::SessionMetaLine;
use codex_protocol::protocol::SessionSource;
use serde_json::json;
use std::fs;
use std::path::Path;
use std::path::PathBuf;
use uuid::Uuid;
/// Create a minimal rollout file under `CODEX_HOME/sessions/YYYY/MM/DD/`.
///
/// - `filename_ts` is the filename timestamp component in `YYYY-MM-DDThh-mm-ss` format.
/// - `meta_rfc3339` is the envelope timestamp used in JSON lines.
/// - `preview` is the user message preview text.
/// - `model_provider` optionally sets the provider in the session meta payload.
///
/// Returns the generated conversation/session UUID as a string.
pub fn create_fake_rollout(
codex_home: &Path,
filename_ts: &str,
meta_rfc3339: &str,
preview: &str,
model_provider: Option<&str>,
git_info: Option<GitInfo>,
) -> Result<String> {
let uuid = Uuid::new_v4();
let uuid_str = uuid.to_string();
let conversation_id = ThreadId::from_string(&uuid_str)?;
// sessions/YYYY/MM/DD derived from filename_ts (YYYY-MM-DDThh-mm-ss)
let year = &filename_ts[0..4];
let month = &filename_ts[5..7];
let day = &filename_ts[8..10];
let dir = codex_home.join("sessions").join(year).join(month).join(day);
fs::create_dir_all(&dir)?;
let file_path = dir.join(format!("rollout-{filename_ts}-{uuid}.jsonl"));
// Build JSONL lines
let meta = SessionMeta {
id: conversation_id,
timestamp: meta_rfc3339.to_string(),
cwd: PathBuf::from("/"),
originator: "codex".to_string(),
cli_version: "0.0.0".to_string(),
instructions: None,
source: SessionSource::Cli,
model_provider: model_provider.map(str::to_string),
};
let payload = serde_json::to_value(SessionMetaLine {
meta,
git: git_info,
})?;
let lines = [
json!({
"timestamp": meta_rfc3339,
"type": "session_meta",
"payload": payload
})
.to_string(),
json!({
"timestamp": meta_rfc3339,
"type":"response_item",
"payload": {
"type":"message",
"role":"user",
"content":[{"type":"input_text","text": preview}]
}
})
.to_string(),
json!({
"timestamp": meta_rfc3339,
"type":"event_msg",
"payload": {
"type":"user_message",
"message": preview,
"kind": "plain"
}
})
.to_string(),
];
fs::write(file_path, lines.join("\n") + "\n")?;
Ok(uuid_str)
}

View File

@@ -1,5 +1,4 @@
use std::path::Path;
use anyhow::Result;
use app_test_support::McpProcess;
use app_test_support::to_response;
use codex_app_server_protocol::ArchiveConversationParams;
@@ -9,45 +8,37 @@ use codex_app_server_protocol::NewConversationParams;
use codex_app_server_protocol::NewConversationResponse;
use codex_app_server_protocol::RequestId;
use codex_core::ARCHIVED_SESSIONS_SUBDIR;
use std::path::Path;
use tempfile::TempDir;
use tokio::time::timeout;
const DEFAULT_READ_TIMEOUT: std::time::Duration = std::time::Duration::from_secs(10);
const DEFAULT_READ_TIMEOUT: std::time::Duration = std::time::Duration::from_secs(20);
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
async fn archive_conversation_moves_rollout_into_archived_directory() {
let codex_home = TempDir::new().expect("create temp dir");
create_config_toml(codex_home.path()).expect("write config.toml");
async fn archive_conversation_moves_rollout_into_archived_directory() -> Result<()> {
let codex_home = TempDir::new()?;
create_config_toml(codex_home.path())?;
let mut mcp = McpProcess::new(codex_home.path())
.await
.expect("spawn mcp process");
timeout(DEFAULT_READ_TIMEOUT, mcp.initialize())
.await
.expect("initialize timeout")
.expect("initialize request");
let mut mcp = McpProcess::new(codex_home.path()).await?;
timeout(DEFAULT_READ_TIMEOUT, mcp.initialize()).await??;
let new_request_id = mcp
.send_new_conversation_request(NewConversationParams {
model: Some("mock-model".to_string()),
..Default::default()
})
.await
.expect("send newConversation");
.await?;
let new_response: JSONRPCResponse = timeout(
DEFAULT_READ_TIMEOUT,
mcp.read_stream_until_response_message(RequestId::Integer(new_request_id)),
)
.await
.expect("newConversation timeout")
.expect("newConversation response");
.await??;
let NewConversationResponse {
conversation_id,
rollout_path,
..
} = to_response::<NewConversationResponse>(new_response)
.expect("deserialize newConversation response");
} = to_response::<NewConversationResponse>(new_response)?;
assert!(
rollout_path.exists(),
@@ -60,19 +51,15 @@ async fn archive_conversation_moves_rollout_into_archived_directory() {
conversation_id,
rollout_path: rollout_path.clone(),
})
.await
.expect("send archiveConversation");
.await?;
let archive_response: JSONRPCResponse = timeout(
DEFAULT_READ_TIMEOUT,
mcp.read_stream_until_response_message(RequestId::Integer(archive_request_id)),
)
.await
.expect("archiveConversation timeout")
.expect("archiveConversation response");
.await??;
let _: ArchiveConversationResponse =
to_response::<ArchiveConversationResponse>(archive_response)
.expect("deserialize archiveConversation response");
to_response::<ArchiveConversationResponse>(archive_response)?;
let archived_directory = codex_home.path().join(ARCHIVED_SESSIONS_SUBDIR);
let archived_rollout_path =
@@ -90,6 +77,8 @@ async fn archive_conversation_moves_rollout_into_archived_directory() {
"expected archived rollout path {} to exist",
archived_rollout_path.display()
);
Ok(())
}
fn create_config_toml(codex_home: &Path) -> std::io::Result<()> {

View File

@@ -1,5 +1,4 @@
use std::path::Path;
use anyhow::Result;
use app_test_support::McpProcess;
use app_test_support::to_response;
use codex_app_server_protocol::AuthMode;
@@ -11,6 +10,7 @@ use codex_app_server_protocol::LoginApiKeyParams;
use codex_app_server_protocol::LoginApiKeyResponse;
use codex_app_server_protocol::RequestId;
use pretty_assertions::assert_eq;
use std::path::Path;
use tempfile::TempDir;
use tokio::time::timeout;
@@ -37,7 +37,7 @@ model_provider = "mock_provider"
[model_providers.mock_provider]
name = "Mock provider for test"
base_url = "http://127.0.0.1:0/v1"
wire_api = "chat"
wire_api = "responses"
request_max_retries = 0
stream_max_retries = 0
{requires_line}
@@ -71,125 +71,99 @@ forced_login_method = "{forced_method}"
std::fs::write(config_toml, contents)
}
async fn login_with_api_key_via_request(mcp: &mut McpProcess, api_key: &str) {
async fn login_with_api_key_via_request(mcp: &mut McpProcess, api_key: &str) -> Result<()> {
let request_id = mcp
.send_login_api_key_request(LoginApiKeyParams {
api_key: api_key.to_string(),
})
.await
.unwrap_or_else(|e| panic!("send loginApiKey: {e}"));
.await?;
let resp: JSONRPCResponse = timeout(
DEFAULT_READ_TIMEOUT,
mcp.read_stream_until_response_message(RequestId::Integer(request_id)),
)
.await
.unwrap_or_else(|e| panic!("loginApiKey timeout: {e}"))
.unwrap_or_else(|e| panic!("loginApiKey response: {e}"));
let _: LoginApiKeyResponse =
to_response(resp).unwrap_or_else(|e| panic!("deserialize login response: {e}"));
.await??;
let _: LoginApiKeyResponse = to_response(resp)?;
Ok(())
}
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
async fn get_auth_status_no_auth() {
let codex_home = TempDir::new().unwrap_or_else(|e| panic!("create tempdir: {e}"));
create_config_toml(codex_home.path()).unwrap_or_else(|err| panic!("write config.toml: {err}"));
async fn get_auth_status_no_auth() -> Result<()> {
let codex_home = TempDir::new()?;
create_config_toml(codex_home.path())?;
let mut mcp = McpProcess::new_with_env(codex_home.path(), &[("OPENAI_API_KEY", None)])
.await
.expect("spawn mcp process");
timeout(DEFAULT_READ_TIMEOUT, mcp.initialize())
.await
.expect("init timeout")
.expect("init failed");
let mut mcp = McpProcess::new_with_env(codex_home.path(), &[("OPENAI_API_KEY", None)]).await?;
timeout(DEFAULT_READ_TIMEOUT, mcp.initialize()).await??;
let request_id = mcp
.send_get_auth_status_request(GetAuthStatusParams {
include_token: Some(true),
refresh_token: Some(false),
})
.await
.expect("send getAuthStatus");
.await?;
let resp: JSONRPCResponse = timeout(
DEFAULT_READ_TIMEOUT,
mcp.read_stream_until_response_message(RequestId::Integer(request_id)),
)
.await
.expect("getAuthStatus timeout")
.expect("getAuthStatus response");
let status: GetAuthStatusResponse = to_response(resp).expect("deserialize status");
.await??;
let status: GetAuthStatusResponse = to_response(resp)?;
assert_eq!(status.auth_method, None, "expected no auth method");
assert_eq!(status.auth_token, None, "expected no token");
Ok(())
}
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
async fn get_auth_status_with_api_key() {
let codex_home = TempDir::new().unwrap_or_else(|e| panic!("create tempdir: {e}"));
create_config_toml(codex_home.path()).unwrap_or_else(|err| panic!("write config.toml: {err}"));
async fn get_auth_status_with_api_key() -> Result<()> {
let codex_home = TempDir::new()?;
create_config_toml(codex_home.path())?;
let mut mcp = McpProcess::new(codex_home.path())
.await
.expect("spawn mcp process");
timeout(DEFAULT_READ_TIMEOUT, mcp.initialize())
.await
.expect("init timeout")
.expect("init failed");
let mut mcp = McpProcess::new(codex_home.path()).await?;
timeout(DEFAULT_READ_TIMEOUT, mcp.initialize()).await??;
login_with_api_key_via_request(&mut mcp, "sk-test-key").await;
login_with_api_key_via_request(&mut mcp, "sk-test-key").await?;
let request_id = mcp
.send_get_auth_status_request(GetAuthStatusParams {
include_token: Some(true),
refresh_token: Some(false),
})
.await
.expect("send getAuthStatus");
.await?;
let resp: JSONRPCResponse = timeout(
DEFAULT_READ_TIMEOUT,
mcp.read_stream_until_response_message(RequestId::Integer(request_id)),
)
.await
.expect("getAuthStatus timeout")
.expect("getAuthStatus response");
let status: GetAuthStatusResponse = to_response(resp).expect("deserialize status");
.await??;
let status: GetAuthStatusResponse = to_response(resp)?;
assert_eq!(status.auth_method, Some(AuthMode::ApiKey));
assert_eq!(status.auth_token, Some("sk-test-key".to_string()));
Ok(())
}
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
async fn get_auth_status_with_api_key_when_auth_not_required() {
let codex_home = TempDir::new().unwrap_or_else(|e| panic!("create tempdir: {e}"));
create_config_toml_custom_provider(codex_home.path(), false)
.unwrap_or_else(|err| panic!("write config.toml: {err}"));
async fn get_auth_status_with_api_key_when_auth_not_required() -> Result<()> {
let codex_home = TempDir::new()?;
create_config_toml_custom_provider(codex_home.path(), false)?;
let mut mcp = McpProcess::new(codex_home.path())
.await
.expect("spawn mcp process");
timeout(DEFAULT_READ_TIMEOUT, mcp.initialize())
.await
.expect("init timeout")
.expect("init failed");
let mut mcp = McpProcess::new(codex_home.path()).await?;
timeout(DEFAULT_READ_TIMEOUT, mcp.initialize()).await??;
login_with_api_key_via_request(&mut mcp, "sk-test-key").await;
login_with_api_key_via_request(&mut mcp, "sk-test-key").await?;
let request_id = mcp
.send_get_auth_status_request(GetAuthStatusParams {
include_token: Some(true),
refresh_token: Some(false),
})
.await
.expect("send getAuthStatus");
.await?;
let resp: JSONRPCResponse = timeout(
DEFAULT_READ_TIMEOUT,
mcp.read_stream_until_response_message(RequestId::Integer(request_id)),
)
.await
.expect("getAuthStatus timeout")
.expect("getAuthStatus response");
let status: GetAuthStatusResponse = to_response(resp).expect("deserialize status");
.await??;
let status: GetAuthStatusResponse = to_response(resp)?;
assert_eq!(status.auth_method, None, "expected no auth method");
assert_eq!(status.auth_token, None, "expected no token");
assert_eq!(
@@ -197,76 +171,60 @@ async fn get_auth_status_with_api_key_when_auth_not_required() {
Some(false),
"requires_openai_auth should be false",
);
Ok(())
}
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
async fn get_auth_status_with_api_key_no_include_token() {
let codex_home = TempDir::new().unwrap_or_else(|e| panic!("create tempdir: {e}"));
create_config_toml(codex_home.path()).unwrap_or_else(|err| panic!("write config.toml: {err}"));
async fn get_auth_status_with_api_key_no_include_token() -> Result<()> {
let codex_home = TempDir::new()?;
create_config_toml(codex_home.path())?;
let mut mcp = McpProcess::new(codex_home.path())
.await
.expect("spawn mcp process");
timeout(DEFAULT_READ_TIMEOUT, mcp.initialize())
.await
.expect("init timeout")
.expect("init failed");
let mut mcp = McpProcess::new(codex_home.path()).await?;
timeout(DEFAULT_READ_TIMEOUT, mcp.initialize()).await??;
login_with_api_key_via_request(&mut mcp, "sk-test-key").await;
login_with_api_key_via_request(&mut mcp, "sk-test-key").await?;
// Build params via struct so None field is omitted in wire JSON.
let params = GetAuthStatusParams {
include_token: None,
refresh_token: Some(false),
};
let request_id = mcp
.send_get_auth_status_request(params)
.await
.expect("send getAuthStatus");
let request_id = mcp.send_get_auth_status_request(params).await?;
let resp: JSONRPCResponse = timeout(
DEFAULT_READ_TIMEOUT,
mcp.read_stream_until_response_message(RequestId::Integer(request_id)),
)
.await
.expect("getAuthStatus timeout")
.expect("getAuthStatus response");
let status: GetAuthStatusResponse = to_response(resp).expect("deserialize status");
.await??;
let status: GetAuthStatusResponse = to_response(resp)?;
assert_eq!(status.auth_method, Some(AuthMode::ApiKey));
assert!(status.auth_token.is_none(), "token must be omitted");
Ok(())
}
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
async fn login_api_key_rejected_when_forced_chatgpt() {
let codex_home = TempDir::new().unwrap_or_else(|e| panic!("create tempdir: {e}"));
create_config_toml_forced_login(codex_home.path(), "chatgpt")
.unwrap_or_else(|err| panic!("write config.toml: {err}"));
async fn login_api_key_rejected_when_forced_chatgpt() -> Result<()> {
let codex_home = TempDir::new()?;
create_config_toml_forced_login(codex_home.path(), "chatgpt")?;
let mut mcp = McpProcess::new(codex_home.path())
.await
.expect("spawn mcp process");
timeout(DEFAULT_READ_TIMEOUT, mcp.initialize())
.await
.expect("init timeout")
.expect("init failed");
let mut mcp = McpProcess::new(codex_home.path()).await?;
timeout(DEFAULT_READ_TIMEOUT, mcp.initialize()).await??;
let request_id = mcp
.send_login_api_key_request(LoginApiKeyParams {
api_key: "sk-test-key".to_string(),
})
.await
.expect("send loginApiKey");
.await?;
let err: JSONRPCError = timeout(
DEFAULT_READ_TIMEOUT,
mcp.read_stream_until_error_message(RequestId::Integer(request_id)),
)
.await
.expect("loginApiKey error timeout")
.expect("loginApiKey error");
.await??;
assert_eq!(
err.error.message,
"API key login is disabled. Use ChatGPT login instead."
);
Ok(())
}

View File

@@ -1,9 +1,9 @@
use std::path::Path;
use anyhow::Result;
use app_test_support::McpProcess;
use app_test_support::create_final_assistant_message_sse_response;
use app_test_support::create_mock_chat_completions_server;
use app_test_support::create_shell_sse_response;
use app_test_support::create_mock_responses_server_sequence;
use app_test_support::create_shell_command_sse_response;
use app_test_support::format_with_current_shell;
use app_test_support::to_response;
use codex_app_server_protocol::AddConversationListenerParams;
use codex_app_server_protocol::AddConversationSubscriptionResponse;
@@ -23,59 +23,54 @@ use codex_app_server_protocol::SendUserTurnResponse;
use codex_app_server_protocol::ServerRequest;
use codex_core::protocol::AskForApproval;
use codex_core::protocol::SandboxPolicy;
use codex_core::protocol_config_types::ReasoningEffort;
use codex_core::protocol_config_types::ReasoningSummary;
use codex_core::spawn::CODEX_SANDBOX_NETWORK_DISABLED_ENV_VAR;
use codex_protocol::config_types::SandboxMode;
use codex_protocol::openai_models::ReasoningEffort;
use codex_protocol::parse_command::ParsedCommand;
use codex_protocol::protocol::Event;
use codex_protocol::protocol::EventMsg;
use codex_protocol::protocol::InputMessageKind;
use pretty_assertions::assert_eq;
use std::env;
use std::path::Path;
use tempfile::TempDir;
use tokio::time::timeout;
const DEFAULT_READ_TIMEOUT: std::time::Duration = std::time::Duration::from_secs(10);
#[tokio::test(flavor = "multi_thread", worker_threads = 4)]
async fn test_codex_jsonrpc_conversation_flow() {
async fn test_codex_jsonrpc_conversation_flow() -> Result<()> {
if env::var(CODEX_SANDBOX_NETWORK_DISABLED_ENV_VAR).is_ok() {
println!(
"Skipping test because it cannot execute when network is disabled in a Codex sandbox."
);
return;
return Ok(());
}
let tmp = TempDir::new().expect("tmp dir");
let tmp = TempDir::new()?;
// Temporary Codex home with config pointing at the mock server.
let codex_home = tmp.path().join("codex_home");
std::fs::create_dir(&codex_home).expect("create codex home dir");
std::fs::create_dir(&codex_home)?;
let working_directory = tmp.path().join("workdir");
std::fs::create_dir(&working_directory).expect("create working directory");
std::fs::create_dir(&working_directory)?;
// Create a mock model server that immediately ends each turn.
// Two turns are expected: initial session configure + one user message.
let responses = vec![
create_shell_sse_response(
create_shell_command_sse_response(
vec!["ls".to_string()],
Some(&working_directory),
Some(5000),
"call1234",
)
.expect("create shell sse response"),
create_final_assistant_message_sse_response("Enjoy your new git repo!")
.expect("create final assistant message"),
)?,
create_final_assistant_message_sse_response("Enjoy your new git repo!")?,
];
let server = create_mock_chat_completions_server(responses).await;
create_config_toml(&codex_home, &server.uri()).expect("write config");
let server = create_mock_responses_server_sequence(responses).await;
create_config_toml(&codex_home, &server.uri())?;
// Start MCP server and initialize.
let mut mcp = McpProcess::new(&codex_home).await.expect("spawn mcp");
timeout(DEFAULT_READ_TIMEOUT, mcp.initialize())
.await
.expect("init timeout")
.expect("init error");
let mut mcp = McpProcess::new(&codex_home).await?;
timeout(DEFAULT_READ_TIMEOUT, mcp.initialize()).await??;
// 1) newConversation
let new_conv_id = mcp
@@ -83,17 +78,13 @@ async fn test_codex_jsonrpc_conversation_flow() {
cwd: Some(working_directory.to_string_lossy().into_owned()),
..Default::default()
})
.await
.expect("send newConversation");
.await?;
let new_conv_resp: JSONRPCResponse = timeout(
DEFAULT_READ_TIMEOUT,
mcp.read_stream_until_response_message(RequestId::Integer(new_conv_id)),
)
.await
.expect("newConversation timeout")
.expect("newConversation resp");
let new_conv_resp = to_response::<NewConversationResponse>(new_conv_resp)
.expect("deserialize newConversation response");
.await??;
let new_conv_resp = to_response::<NewConversationResponse>(new_conv_resp)?;
let NewConversationResponse {
conversation_id,
model,
@@ -104,19 +95,18 @@ async fn test_codex_jsonrpc_conversation_flow() {
// 2) addConversationListener
let add_listener_id = mcp
.send_add_conversation_listener_request(AddConversationListenerParams { conversation_id })
.await
.expect("send addConversationListener");
.send_add_conversation_listener_request(AddConversationListenerParams {
conversation_id,
experimental_raw_events: false,
})
.await?;
let add_listener_resp: JSONRPCResponse = timeout(
DEFAULT_READ_TIMEOUT,
mcp.read_stream_until_response_message(RequestId::Integer(add_listener_id)),
)
.await
.expect("addConversationListener timeout")
.expect("addConversationListener resp");
.await??;
let AddConversationSubscriptionResponse { subscription_id } =
to_response::<AddConversationSubscriptionResponse>(add_listener_resp)
.expect("deserialize addConversationListener response");
to_response::<AddConversationSubscriptionResponse>(add_listener_resp)?;
// 3) sendUserMessage (should trigger notifications; we only validate an OK response)
let send_user_id = mcp
@@ -126,17 +116,13 @@ async fn test_codex_jsonrpc_conversation_flow() {
text: "text".to_string(),
}],
})
.await
.expect("send sendUserMessage");
.await?;
let send_user_resp: JSONRPCResponse = timeout(
DEFAULT_READ_TIMEOUT,
mcp.read_stream_until_response_message(RequestId::Integer(send_user_id)),
)
.await
.expect("sendUserMessage timeout")
.expect("sendUserMessage resp");
let SendUserMessageResponse {} = to_response::<SendUserMessageResponse>(send_user_resp)
.expect("deserialize sendUserMessage response");
.await??;
let SendUserMessageResponse {} = to_response::<SendUserMessageResponse>(send_user_resp)?;
// Verify the task_finished notification is received.
// Note this also ensures that the final request to the server was made.
@@ -144,9 +130,7 @@ async fn test_codex_jsonrpc_conversation_flow() {
DEFAULT_READ_TIMEOUT,
mcp.read_stream_until_notification_message("codex/event/task_complete"),
)
.await
.expect("task_finished_notification timeout")
.expect("task_finished_notification resp");
.await??;
let serde_json::Value::Object(map) = task_finished_notification
.params
.expect("notification should have params")
@@ -161,40 +145,36 @@ async fn test_codex_jsonrpc_conversation_flow() {
// 4) removeConversationListener
let remove_listener_id = mcp
.send_remove_conversation_listener_request(RemoveConversationListenerParams {
subscription_id,
})
.await
.expect("send removeConversationListener");
.send_remove_thread_listener_request(RemoveConversationListenerParams { subscription_id })
.await?;
let remove_listener_resp: JSONRPCResponse = timeout(
DEFAULT_READ_TIMEOUT,
mcp.read_stream_until_response_message(RequestId::Integer(remove_listener_id)),
)
.await
.expect("removeConversationListener timeout")
.expect("removeConversationListener resp");
let RemoveConversationSubscriptionResponse {} =
to_response(remove_listener_resp).expect("deserialize removeConversationListener response");
.await??;
let RemoveConversationSubscriptionResponse {} = to_response(remove_listener_resp)?;
Ok(())
}
#[tokio::test(flavor = "multi_thread", worker_threads = 4)]
async fn test_send_user_turn_changes_approval_policy_behavior() {
async fn test_send_user_turn_changes_approval_policy_behavior() -> Result<()> {
if env::var(CODEX_SANDBOX_NETWORK_DISABLED_ENV_VAR).is_ok() {
println!(
"Skipping test because it cannot execute when network is disabled in a Codex sandbox."
);
return;
return Ok(());
}
let tmp = TempDir::new().expect("tmp dir");
let tmp = TempDir::new()?;
let codex_home = tmp.path().join("codex_home");
std::fs::create_dir(&codex_home).expect("create codex home dir");
std::fs::create_dir(&codex_home)?;
let working_directory = tmp.path().join("workdir");
std::fs::create_dir(&working_directory).expect("create working directory");
std::fs::create_dir(&working_directory)?;
// Mock server will request a python shell call for the first and second turn, then finish.
let responses = vec![
create_shell_sse_response(
create_shell_command_sse_response(
vec![
"python3".to_string(),
"-c".to_string(),
@@ -203,11 +183,9 @@ async fn test_send_user_turn_changes_approval_policy_behavior() {
Some(&working_directory),
Some(5000),
"call1",
)
.expect("create first shell sse response"),
create_final_assistant_message_sse_response("done 1")
.expect("create final assistant message 1"),
create_shell_sse_response(
)?,
create_final_assistant_message_sse_response("done 1")?,
create_shell_command_sse_response(
vec![
"python3".to_string(),
"-c".to_string(),
@@ -216,20 +194,15 @@ async fn test_send_user_turn_changes_approval_policy_behavior() {
Some(&working_directory),
Some(5000),
"call2",
)
.expect("create second shell sse response"),
create_final_assistant_message_sse_response("done 2")
.expect("create final assistant message 2"),
)?,
create_final_assistant_message_sse_response("done 2")?,
];
let server = create_mock_chat_completions_server(responses).await;
create_config_toml(&codex_home, &server.uri()).expect("write config");
let server = create_mock_responses_server_sequence(responses).await;
create_config_toml(&codex_home, &server.uri())?;
// Start MCP server and initialize.
let mut mcp = McpProcess::new(&codex_home).await.expect("spawn mcp");
timeout(DEFAULT_READ_TIMEOUT, mcp.initialize())
.await
.expect("init timeout")
.expect("init error");
let mut mcp = McpProcess::new(&codex_home).await?;
timeout(DEFAULT_READ_TIMEOUT, mcp.initialize()).await??;
// 1) Start conversation with approval_policy=untrusted
let new_conv_id = mcp
@@ -237,36 +210,30 @@ async fn test_send_user_turn_changes_approval_policy_behavior() {
cwd: Some(working_directory.to_string_lossy().into_owned()),
..Default::default()
})
.await
.expect("send newConversation");
.await?;
let new_conv_resp: JSONRPCResponse = timeout(
DEFAULT_READ_TIMEOUT,
mcp.read_stream_until_response_message(RequestId::Integer(new_conv_id)),
)
.await
.expect("newConversation timeout")
.expect("newConversation resp");
.await??;
let NewConversationResponse {
conversation_id, ..
} = to_response::<NewConversationResponse>(new_conv_resp)
.expect("deserialize newConversation response");
} = to_response::<NewConversationResponse>(new_conv_resp)?;
// 2) addConversationListener
let add_listener_id = mcp
.send_add_conversation_listener_request(AddConversationListenerParams { conversation_id })
.await
.expect("send addConversationListener");
let _: AddConversationSubscriptionResponse =
to_response::<AddConversationSubscriptionResponse>(
timeout(
DEFAULT_READ_TIMEOUT,
mcp.read_stream_until_response_message(RequestId::Integer(add_listener_id)),
)
.await
.expect("addConversationListener timeout")
.expect("addConversationListener resp"),
.send_add_conversation_listener_request(AddConversationListenerParams {
conversation_id,
experimental_raw_events: false,
})
.await?;
let _: AddConversationSubscriptionResponse = to_response::<AddConversationSubscriptionResponse>(
timeout(
DEFAULT_READ_TIMEOUT,
mcp.read_stream_until_response_message(RequestId::Integer(add_listener_id)),
)
.expect("deserialize addConversationListener response");
.await??,
)?;
// 3) sendUserMessage triggers a shell call; approval policy is Untrusted so we should get an elicitation
let send_user_id = mcp
@@ -276,27 +243,21 @@ async fn test_send_user_turn_changes_approval_policy_behavior() {
text: "run python".to_string(),
}],
})
.await
.expect("send sendUserMessage");
.await?;
let _send_user_resp: SendUserMessageResponse = to_response::<SendUserMessageResponse>(
timeout(
DEFAULT_READ_TIMEOUT,
mcp.read_stream_until_response_message(RequestId::Integer(send_user_id)),
)
.await
.expect("sendUserMessage timeout")
.expect("sendUserMessage resp"),
)
.expect("deserialize sendUserMessage response");
.await??,
)?;
// Expect an ExecCommandApproval request (elicitation)
let request = timeout(
DEFAULT_READ_TIMEOUT,
mcp.read_stream_until_request_message(),
)
.await
.expect("waiting for exec approval request timeout")
.expect("exec approval request");
.await??;
let ServerRequest::ExecCommandApproval { request_id, params } = request else {
panic!("expected ExecCommandApproval request, got: {request:?}");
};
@@ -305,11 +266,7 @@ async fn test_send_user_turn_changes_approval_policy_behavior() {
ExecCommandApprovalParams {
conversation_id,
call_id: "call1".to_string(),
command: vec![
"python3".to_string(),
"-c".to_string(),
"print(42)".to_string(),
],
command: format_with_current_shell("python3 -c 'print(42)'"),
cwd: working_directory.clone(),
reason: None,
parsed_cmd: vec![ParsedCommand::Unknown {
@@ -324,17 +281,14 @@ async fn test_send_user_turn_changes_approval_policy_behavior() {
request_id,
serde_json::json!({ "decision": codex_core::protocol::ReviewDecision::Approved }),
)
.await
.expect("send approval response");
.await?;
// Wait for first TaskComplete
// Wait for first TurnComplete
let _ = timeout(
DEFAULT_READ_TIMEOUT,
mcp.read_stream_until_notification_message("codex/event/task_complete"),
)
.await
.expect("task_complete 1 timeout")
.expect("task_complete 1 notification");
.await??;
// 4) sendUserTurn with approval_policy=never should run without elicitation
let send_turn_id = mcp
@@ -349,20 +303,17 @@ async fn test_send_user_turn_changes_approval_policy_behavior() {
model: "mock-model".to_string(),
effort: Some(ReasoningEffort::Medium),
summary: ReasoningSummary::Auto,
output_schema: None,
})
.await
.expect("send sendUserTurn");
.await?;
// Acknowledge sendUserTurn
let _send_turn_resp: SendUserTurnResponse = to_response::<SendUserTurnResponse>(
timeout(
DEFAULT_READ_TIMEOUT,
mcp.read_stream_until_response_message(RequestId::Integer(send_turn_id)),
)
.await
.expect("sendUserTurn timeout")
.expect("sendUserTurn resp"),
)
.expect("deserialize sendUserTurn response");
.await??,
)?;
// Ensure we do NOT receive an ExecCommandApproval request before the task completes.
// If any Request is seen while waiting for task_complete, the helper will error and the test fails.
@@ -370,70 +321,53 @@ async fn test_send_user_turn_changes_approval_policy_behavior() {
DEFAULT_READ_TIMEOUT,
mcp.read_stream_until_notification_message("codex/event/task_complete"),
)
.await
.expect("task_complete 2 timeout")
.expect("task_complete 2 notification");
.await??;
Ok(())
}
// Helper: minimal config.toml pointing at mock provider.
#[tokio::test(flavor = "multi_thread", worker_threads = 4)]
async fn test_send_user_turn_updates_sandbox_and_cwd_between_turns() {
async fn test_send_user_turn_updates_sandbox_and_cwd_between_turns() -> Result<()> {
if env::var(CODEX_SANDBOX_NETWORK_DISABLED_ENV_VAR).is_ok() {
println!(
"Skipping test because it cannot execute when network is disabled in a Codex sandbox."
);
return;
return Ok(());
}
let tmp = TempDir::new().expect("tmp dir");
let tmp = TempDir::new()?;
let codex_home = tmp.path().join("codex_home");
std::fs::create_dir(&codex_home).expect("create codex home dir");
std::fs::create_dir(&codex_home)?;
let workspace_root = tmp.path().join("workspace");
std::fs::create_dir(&workspace_root).expect("create workspace root");
std::fs::create_dir(&workspace_root)?;
let first_cwd = workspace_root.join("turn1");
let second_cwd = workspace_root.join("turn2");
std::fs::create_dir(&first_cwd).expect("create first cwd");
std::fs::create_dir(&second_cwd).expect("create second cwd");
std::fs::create_dir(&first_cwd)?;
std::fs::create_dir(&second_cwd)?;
let responses = vec![
create_shell_sse_response(
vec![
"bash".to_string(),
"-lc".to_string(),
"echo first turn".to_string(),
],
create_shell_command_sse_response(
vec!["echo".to_string(), "first".to_string(), "turn".to_string()],
None,
Some(5000),
"call-first",
)
.expect("create first shell response"),
create_final_assistant_message_sse_response("done first")
.expect("create first final assistant message"),
create_shell_sse_response(
vec![
"bash".to_string(),
"-lc".to_string(),
"echo second turn".to_string(),
],
)?,
create_final_assistant_message_sse_response("done first")?,
create_shell_command_sse_response(
vec!["echo".to_string(), "second".to_string(), "turn".to_string()],
None,
Some(5000),
"call-second",
)
.expect("create second shell response"),
create_final_assistant_message_sse_response("done second")
.expect("create second final assistant message"),
)?,
create_final_assistant_message_sse_response("done second")?,
];
let server = create_mock_chat_completions_server(responses).await;
create_config_toml(&codex_home, &server.uri()).expect("write config");
let server = create_mock_responses_server_sequence(responses).await;
create_config_toml(&codex_home, &server.uri())?;
let mut mcp = McpProcess::new(&codex_home)
.await
.expect("spawn mcp process");
timeout(DEFAULT_READ_TIMEOUT, mcp.initialize())
.await
.expect("init timeout")
.expect("init failed");
let mut mcp = McpProcess::new(&codex_home).await?;
timeout(DEFAULT_READ_TIMEOUT, mcp.initialize()).await??;
let new_conv_id = mcp
.send_new_conversation_request(NewConversationParams {
@@ -442,33 +376,29 @@ async fn test_send_user_turn_updates_sandbox_and_cwd_between_turns() {
sandbox: Some(SandboxMode::WorkspaceWrite),
..Default::default()
})
.await
.expect("send newConversation");
.await?;
let new_conv_resp: JSONRPCResponse = timeout(
DEFAULT_READ_TIMEOUT,
mcp.read_stream_until_response_message(RequestId::Integer(new_conv_id)),
)
.await
.expect("newConversation timeout")
.expect("newConversation resp");
.await??;
let NewConversationResponse {
conversation_id,
model,
..
} = to_response::<NewConversationResponse>(new_conv_resp)
.expect("deserialize newConversation response");
} = to_response::<NewConversationResponse>(new_conv_resp)?;
let add_listener_id = mcp
.send_add_conversation_listener_request(AddConversationListenerParams { conversation_id })
.await
.expect("send addConversationListener");
.send_add_conversation_listener_request(AddConversationListenerParams {
conversation_id,
experimental_raw_events: false,
})
.await?;
timeout(
DEFAULT_READ_TIMEOUT,
mcp.read_stream_until_response_message(RequestId::Integer(add_listener_id)),
)
.await
.expect("addConversationListener timeout")
.expect("addConversationListener resp");
.await??;
let first_turn_id = mcp
.send_send_user_turn_request(SendUserTurnParams {
@@ -479,7 +409,7 @@ async fn test_send_user_turn_updates_sandbox_and_cwd_between_turns() {
cwd: first_cwd.clone(),
approval_policy: AskForApproval::Never,
sandbox_policy: SandboxPolicy::WorkspaceWrite {
writable_roots: vec![first_cwd.clone()],
writable_roots: vec![first_cwd.try_into()?],
network_access: false,
exclude_tmpdir_env_var: false,
exclude_slash_tmp: false,
@@ -487,23 +417,20 @@ async fn test_send_user_turn_updates_sandbox_and_cwd_between_turns() {
model: model.clone(),
effort: Some(ReasoningEffort::Medium),
summary: ReasoningSummary::Auto,
output_schema: None,
})
.await
.expect("send first sendUserTurn");
.await?;
timeout(
DEFAULT_READ_TIMEOUT,
mcp.read_stream_until_response_message(RequestId::Integer(first_turn_id)),
)
.await
.expect("sendUserTurn 1 timeout")
.expect("sendUserTurn 1 resp");
.await??;
timeout(
DEFAULT_READ_TIMEOUT,
mcp.read_stream_until_notification_message("codex/event/task_complete"),
)
.await
.expect("task_complete 1 timeout")
.expect("task_complete 1 notification");
.await??;
mcp.clear_message_buffer();
let second_turn_id = mcp
.send_send_user_turn_request(SendUserTurnParams {
@@ -517,61 +444,20 @@ async fn test_send_user_turn_updates_sandbox_and_cwd_between_turns() {
model: model.clone(),
effort: Some(ReasoningEffort::Medium),
summary: ReasoningSummary::Auto,
output_schema: None,
})
.await
.expect("send second sendUserTurn");
.await?;
timeout(
DEFAULT_READ_TIMEOUT,
mcp.read_stream_until_response_message(RequestId::Integer(second_turn_id)),
)
.await
.expect("sendUserTurn 2 timeout")
.expect("sendUserTurn 2 resp");
let mut env_message: Option<String> = None;
let second_cwd_str = second_cwd.to_string_lossy().into_owned();
for _ in 0..10 {
let notification = timeout(
DEFAULT_READ_TIMEOUT,
mcp.read_stream_until_notification_message("codex/event/user_message"),
)
.await
.expect("user_message timeout")
.expect("user_message notification");
let params = notification
.params
.clone()
.expect("user_message should include params");
let event: Event = serde_json::from_value(params).expect("deserialize user_message event");
if let EventMsg::UserMessage(user) = event.msg
&& matches!(user.kind, Some(InputMessageKind::EnvironmentContext))
&& user.message.contains(&second_cwd_str)
{
env_message = Some(user.message);
break;
}
}
let env_message = env_message.expect("expected environment context update");
assert!(
env_message.contains("<sandbox_mode>danger-full-access</sandbox_mode>"),
"env context should reflect new sandbox mode: {env_message}"
);
assert!(
env_message.contains("<network_access>enabled</network_access>"),
"env context should enable network access for danger-full-access policy: {env_message}"
);
assert!(
env_message.contains(&second_cwd_str),
"env context should include updated cwd: {env_message}"
);
.await??;
let exec_begin_notification = timeout(
DEFAULT_READ_TIMEOUT,
mcp.read_stream_until_notification_message("codex/event/exec_command_begin"),
)
.await
.expect("exec_command_begin timeout")
.expect("exec_command_begin notification");
.await??;
let params = exec_begin_notification
.params
.clone()
@@ -585,13 +471,9 @@ async fn test_send_user_turn_updates_sandbox_and_cwd_between_turns() {
exec_begin.cwd, second_cwd,
"exec turn should run from updated cwd"
);
let expected_command = format_with_current_shell("echo second turn");
assert_eq!(
exec_begin.command,
vec![
"bash".to_string(),
"-lc".to_string(),
"echo second turn".to_string()
],
exec_begin.command, expected_command,
"exec turn should run expected command"
);
@@ -599,9 +481,9 @@ async fn test_send_user_turn_updates_sandbox_and_cwd_between_turns() {
DEFAULT_READ_TIMEOUT,
mcp.read_stream_until_notification_message("codex/event/task_complete"),
)
.await
.expect("task_complete 2 timeout")
.expect("task_complete 2 notification");
.await??;
Ok(())
}
fn create_config_toml(codex_home: &Path, server_uri: &str) -> std::io::Result<()> {
@@ -618,7 +500,7 @@ model_provider = "mock_provider"
[model_providers.mock_provider]
name = "Mock provider for test"
base_url = "{server_uri}/v1"
wire_api = "chat"
wire_api = "responses"
request_max_retries = 0
stream_max_retries = 0
"#

View File

@@ -1,7 +1,6 @@
use std::collections::HashMap;
use std::path::Path;
use anyhow::Result;
use app_test_support::McpProcess;
use app_test_support::test_tmp_path;
use app_test_support::to_response;
use codex_app_server_protocol::GetUserSavedConfigResponse;
use codex_app_server_protocol::JSONRPCResponse;
@@ -12,22 +11,26 @@ use codex_app_server_protocol::Tools;
use codex_app_server_protocol::UserSavedConfig;
use codex_core::protocol::AskForApproval;
use codex_protocol::config_types::ForcedLoginMethod;
use codex_protocol::config_types::ReasoningEffort;
use codex_protocol::config_types::ReasoningSummary;
use codex_protocol::config_types::SandboxMode;
use codex_protocol::config_types::Verbosity;
use codex_protocol::openai_models::ReasoningEffort;
use pretty_assertions::assert_eq;
use std::collections::HashMap;
use std::path::Path;
use tempfile::TempDir;
use tokio::time::timeout;
const DEFAULT_READ_TIMEOUT: std::time::Duration = std::time::Duration::from_secs(10);
fn create_config_toml(codex_home: &Path) -> std::io::Result<()> {
let writable_root = test_tmp_path();
let config_toml = codex_home.join("config.toml");
std::fs::write(
config_toml,
r#"
model = "gpt-5-codex"
format!(
r#"
model = "gpt-5.1-codex-max"
approval_policy = "on-request"
sandbox_mode = "workspace-write"
model_reasoning_summary = "detailed"
@@ -38,7 +41,7 @@ forced_chatgpt_workspace_id = "12345678-0000-0000-0000-000000000000"
forced_login_method = "chatgpt"
[sandbox_workspace_write]
writable_roots = ["/tmp"]
writable_roots = [{}]
network_access = true
exclude_tmpdir_env_var = true
exclude_slash_tmp = true
@@ -56,48 +59,41 @@ model_verbosity = "medium"
model_provider = "openai"
chatgpt_base_url = "https://api.chatgpt.com"
"#,
serde_json::json!(writable_root)
),
)
}
#[tokio::test(flavor = "multi_thread", worker_threads = 4)]
async fn get_config_toml_parses_all_fields() {
let codex_home = TempDir::new().unwrap_or_else(|e| panic!("create tempdir: {e}"));
create_config_toml(codex_home.path()).expect("write config.toml");
async fn get_config_toml_parses_all_fields() -> Result<()> {
let codex_home = TempDir::new()?;
create_config_toml(codex_home.path())?;
let mut mcp = McpProcess::new(codex_home.path())
.await
.expect("spawn mcp process");
timeout(DEFAULT_READ_TIMEOUT, mcp.initialize())
.await
.expect("init timeout")
.expect("init failed");
let mut mcp = McpProcess::new(codex_home.path()).await?;
timeout(DEFAULT_READ_TIMEOUT, mcp.initialize()).await??;
let request_id = mcp
.send_get_user_saved_config_request()
.await
.expect("send getUserSavedConfig");
let request_id = mcp.send_get_user_saved_config_request().await?;
let resp: JSONRPCResponse = timeout(
DEFAULT_READ_TIMEOUT,
mcp.read_stream_until_response_message(RequestId::Integer(request_id)),
)
.await
.expect("getUserSavedConfig timeout")
.expect("getUserSavedConfig response");
.await??;
let config: GetUserSavedConfigResponse = to_response(resp).expect("deserialize config");
let config: GetUserSavedConfigResponse = to_response(resp)?;
let writable_root = test_tmp_path();
let expected = GetUserSavedConfigResponse {
config: UserSavedConfig {
approval_policy: Some(AskForApproval::OnRequest),
sandbox_mode: Some(SandboxMode::WorkspaceWrite),
sandbox_settings: Some(SandboxSettings {
writable_roots: vec!["/tmp".into()],
writable_roots: vec![writable_root],
network_access: Some(true),
exclude_tmpdir_env_var: Some(true),
exclude_slash_tmp: Some(true),
}),
forced_chatgpt_workspace_id: Some("12345678-0000-0000-0000-000000000000".into()),
forced_login_method: Some(ForcedLoginMethod::Chatgpt),
model: Some("gpt-5-codex".into()),
model: Some("gpt-5.1-codex-max".into()),
model_reasoning_effort: Some(ReasoningEffort::High),
model_reasoning_summary: Some(ReasoningSummary::Detailed),
model_verbosity: Some(Verbosity::Medium),
@@ -122,33 +118,24 @@ async fn get_config_toml_parses_all_fields() {
};
assert_eq!(config, expected);
Ok(())
}
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
async fn get_config_toml_empty() {
let codex_home = TempDir::new().unwrap_or_else(|e| panic!("create tempdir: {e}"));
async fn get_config_toml_empty() -> Result<()> {
let codex_home = TempDir::new()?;
let mut mcp = McpProcess::new(codex_home.path())
.await
.expect("spawn mcp process");
timeout(DEFAULT_READ_TIMEOUT, mcp.initialize())
.await
.expect("init timeout")
.expect("init failed");
let mut mcp = McpProcess::new(codex_home.path()).await?;
timeout(DEFAULT_READ_TIMEOUT, mcp.initialize()).await??;
let request_id = mcp
.send_get_user_saved_config_request()
.await
.expect("send getUserSavedConfig");
let request_id = mcp.send_get_user_saved_config_request().await?;
let resp: JSONRPCResponse = timeout(
DEFAULT_READ_TIMEOUT,
mcp.read_stream_until_response_message(RequestId::Integer(request_id)),
)
.await
.expect("getUserSavedConfig timeout")
.expect("getUserSavedConfig response");
.await??;
let config: GetUserSavedConfigResponse = to_response(resp).expect("deserialize config");
let config: GetUserSavedConfigResponse = to_response(resp)?;
let expected = GetUserSavedConfigResponse {
config: UserSavedConfig {
approval_policy: None,
@@ -167,4 +154,5 @@ async fn get_config_toml_empty() {
};
assert_eq!(config, expected);
Ok(())
}

View File

@@ -1,8 +1,6 @@
use std::path::Path;
use anyhow::Result;
use app_test_support::McpProcess;
use app_test_support::create_final_assistant_message_sse_response;
use app_test_support::create_mock_chat_completions_server;
use app_test_support::to_response;
use codex_app_server_protocol::AddConversationListenerParams;
use codex_app_server_protocol::AddConversationSubscriptionResponse;
@@ -13,33 +11,29 @@ use codex_app_server_protocol::NewConversationResponse;
use codex_app_server_protocol::RequestId;
use codex_app_server_protocol::SendUserMessageParams;
use codex_app_server_protocol::SendUserMessageResponse;
use core_test_support::responses;
use pretty_assertions::assert_eq;
use serde_json::json;
use std::path::Path;
use tempfile::TempDir;
use tokio::time::timeout;
const DEFAULT_READ_TIMEOUT: std::time::Duration = std::time::Duration::from_secs(10);
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
async fn test_conversation_create_and_send_message_ok() {
async fn test_conversation_create_and_send_message_ok() -> Result<()> {
// Mock server we won't strictly rely on it, but provide one to satisfy any model wiring.
let responses = vec![
create_final_assistant_message_sse_response("Done").expect("build mock assistant message"),
];
let server = create_mock_chat_completions_server(responses).await;
let response_body = create_final_assistant_message_sse_response("Done")?;
let server = responses::start_mock_server().await;
let response_mock = responses::mount_sse_sequence(&server, vec![response_body]).await;
// Temporary Codex home with config pointing at the mock server.
let codex_home = TempDir::new().expect("create temp dir");
create_config_toml(codex_home.path(), &server.uri()).expect("write config.toml");
let codex_home = TempDir::new()?;
create_config_toml(codex_home.path(), &server.uri())?;
// Start MCP server process and initialize.
let mut mcp = McpProcess::new(codex_home.path())
.await
.expect("spawn mcp process");
timeout(DEFAULT_READ_TIMEOUT, mcp.initialize())
.await
.expect("init timeout")
.expect("init failed");
let mut mcp = McpProcess::new(codex_home.path()).await?;
timeout(DEFAULT_READ_TIMEOUT, mcp.initialize()).await??;
// Create a conversation via the new JSON-RPC API.
let new_conv_id = mcp
@@ -47,40 +41,35 @@ async fn test_conversation_create_and_send_message_ok() {
model: Some("o3".to_string()),
..Default::default()
})
.await
.expect("send newConversation");
.await?;
let new_conv_resp: JSONRPCResponse = timeout(
DEFAULT_READ_TIMEOUT,
mcp.read_stream_until_response_message(RequestId::Integer(new_conv_id)),
)
.await
.expect("newConversation timeout")
.expect("newConversation resp");
.await??;
let NewConversationResponse {
conversation_id,
model,
reasoning_effort: _,
rollout_path: _,
} = to_response::<NewConversationResponse>(new_conv_resp)
.expect("deserialize newConversation response");
} = to_response::<NewConversationResponse>(new_conv_resp)?;
assert_eq!(model, "o3");
// Add a listener so we receive notifications for this conversation (not strictly required for this test).
let add_listener_id = mcp
.send_add_conversation_listener_request(AddConversationListenerParams { conversation_id })
.await
.expect("send addConversationListener");
.send_add_conversation_listener_request(AddConversationListenerParams {
conversation_id,
experimental_raw_events: false,
})
.await?;
let _sub: AddConversationSubscriptionResponse =
to_response::<AddConversationSubscriptionResponse>(
timeout(
DEFAULT_READ_TIMEOUT,
mcp.read_stream_until_response_message(RequestId::Integer(add_listener_id)),
)
.await
.expect("addConversationListener timeout")
.expect("addConversationListener resp"),
)
.expect("deserialize addConversationListener response");
.await??,
)?;
// Now send a user message via the wire API and expect an OK (empty object) result.
let send_id = mcp
@@ -90,46 +79,41 @@ async fn test_conversation_create_and_send_message_ok() {
text: "Hello".to_string(),
}],
})
.await
.expect("send sendUserMessage");
.await?;
let send_resp: JSONRPCResponse = timeout(
DEFAULT_READ_TIMEOUT,
mcp.read_stream_until_response_message(RequestId::Integer(send_id)),
)
.await
.expect("sendUserMessage timeout")
.expect("sendUserMessage resp");
let _ok: SendUserMessageResponse = to_response::<SendUserMessageResponse>(send_resp)
.expect("deserialize sendUserMessage response");
.await??;
let _ok: SendUserMessageResponse = to_response::<SendUserMessageResponse>(send_resp)?;
// avoid race condition by waiting for the mock server to receive the chat.completions request
// Avoid race condition by waiting for the mock server to receive the responses request.
let deadline = std::time::Instant::now() + DEFAULT_READ_TIMEOUT;
loop {
let requests = server.received_requests().await.unwrap_or_default();
let requests = loop {
let requests = response_mock.requests();
if !requests.is_empty() {
break;
break requests;
}
if std::time::Instant::now() >= deadline {
panic!("mock server did not receive the chat.completions request in time");
panic!("mock server did not receive the responses request in time");
}
tokio::time::sleep(std::time::Duration::from_millis(10)).await;
}
};
// Verify the outbound request body matches expectations for Chat Completions.
let request = &server.received_requests().await.unwrap()[0];
let body = request
.body_json::<serde_json::Value>()
.expect("parse request body as JSON");
// Verify the outbound request body matches expectations for Responses.
let request = requests
.first()
.expect("mock server should have received at least one request");
let body = request.body_json();
assert_eq!(body["model"], json!("o3"));
assert!(body["stream"].as_bool().unwrap_or(false));
let messages = body["messages"]
.as_array()
.expect("messages should be array");
let last = messages.last().expect("at least one message");
assert_eq!(last["role"], json!("user"));
assert_eq!(last["content"], json!("Hello"));
let user_texts = request.message_input_texts("user");
assert!(
user_texts.iter().any(|text| text == "Hello"),
"expected user input to include Hello, got {user_texts:?}"
);
drop(server);
Ok(())
}
// Helper to create a config.toml pointing at the mock model server.
@@ -148,7 +132,7 @@ model_provider = "mock_provider"
[model_providers.mock_provider]
name = "Mock provider for test"
base_url = "{server_uri}/v1"
wire_api = "chat"
wire_api = "responses"
request_max_retries = 0
stream_max_retries = 0
"#

View File

@@ -0,0 +1,140 @@
use anyhow::Result;
use app_test_support::McpProcess;
use app_test_support::create_fake_rollout;
use app_test_support::to_response;
use codex_app_server_protocol::ForkConversationParams;
use codex_app_server_protocol::ForkConversationResponse;
use codex_app_server_protocol::JSONRPCNotification;
use codex_app_server_protocol::JSONRPCResponse;
use codex_app_server_protocol::NewConversationParams; // reused for overrides shape
use codex_app_server_protocol::RequestId;
use codex_app_server_protocol::ServerNotification;
use codex_app_server_protocol::SessionConfiguredNotification;
use codex_core::protocol::EventMsg;
use pretty_assertions::assert_eq;
use tempfile::TempDir;
use tokio::time::timeout;
const DEFAULT_READ_TIMEOUT: std::time::Duration = std::time::Duration::from_secs(10);
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
async fn fork_conversation_creates_new_rollout() -> Result<()> {
let codex_home = TempDir::new()?;
let preview = "Hello A";
let conversation_id = create_fake_rollout(
codex_home.path(),
"2025-01-02T12-00-00",
"2025-01-02T12:00:00Z",
preview,
Some("openai"),
None,
)?;
let original_path = codex_home
.path()
.join("sessions")
.join("2025")
.join("01")
.join("02")
.join(format!(
"rollout-2025-01-02T12-00-00-{conversation_id}.jsonl"
));
assert!(
original_path.exists(),
"expected original rollout to exist at {}",
original_path.display()
);
let original_contents = std::fs::read_to_string(&original_path)?;
let mut mcp = McpProcess::new(codex_home.path()).await?;
timeout(DEFAULT_READ_TIMEOUT, mcp.initialize()).await??;
let fork_req_id = mcp
.send_fork_conversation_request(ForkConversationParams {
path: Some(original_path.clone()),
conversation_id: None,
overrides: Some(NewConversationParams {
model: Some("o3".to_string()),
..Default::default()
}),
})
.await?;
// Expect a sessionConfigured notification for the forked session.
let notification: JSONRPCNotification = timeout(
DEFAULT_READ_TIMEOUT,
mcp.read_stream_until_notification_message("sessionConfigured"),
)
.await??;
let session_configured: ServerNotification = notification.try_into()?;
let ServerNotification::SessionConfigured(SessionConfiguredNotification {
model,
session_id,
rollout_path,
initial_messages: session_initial_messages,
..
}) = session_configured
else {
unreachable!("expected sessionConfigured notification");
};
assert_eq!(model, "o3");
assert_ne!(
session_id.to_string(),
conversation_id,
"expected a new conversation id when forking"
);
assert_ne!(
rollout_path, original_path,
"expected a new rollout path when forking"
);
assert!(
rollout_path.exists(),
"expected forked rollout to exist at {}",
rollout_path.display()
);
let session_initial_messages =
session_initial_messages.expect("expected initial messages when forking from rollout");
match session_initial_messages.as_slice() {
[EventMsg::UserMessage(message)] => {
assert_eq!(message.message, preview);
}
other => panic!("unexpected initial messages from rollout fork: {other:#?}"),
}
// Then the response for forkConversation.
let fork_resp: JSONRPCResponse = timeout(
DEFAULT_READ_TIMEOUT,
mcp.read_stream_until_response_message(RequestId::Integer(fork_req_id)),
)
.await??;
let ForkConversationResponse {
conversation_id: forked_id,
model: forked_model,
initial_messages: response_initial_messages,
rollout_path: response_rollout_path,
} = to_response::<ForkConversationResponse>(fork_resp)?;
assert_eq!(forked_model, "o3");
assert_eq!(response_rollout_path, rollout_path);
assert_ne!(forked_id.to_string(), conversation_id);
let response_initial_messages =
response_initial_messages.expect("expected initial messages in fork response");
match response_initial_messages.as_slice() {
[EventMsg::UserMessage(message)] => {
assert_eq!(message.message, preview);
}
other => panic!("unexpected initial messages in fork response: {other:#?}"),
}
let after_contents = std::fs::read_to_string(&original_path)?;
assert_eq!(
after_contents, original_contents,
"fork should not mutate the original rollout file"
);
Ok(())
}

View File

@@ -1,5 +1,5 @@
use anyhow::Context;
use anyhow::Result;
use anyhow::anyhow;
use app_test_support::McpProcess;
use codex_app_server_protocol::JSONRPCResponse;
use codex_app_server_protocol::RequestId;
@@ -13,48 +13,39 @@ const DEFAULT_READ_TIMEOUT: std::time::Duration = std::time::Duration::from_secs
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
async fn test_fuzzy_file_search_sorts_and_includes_indices() -> Result<()> {
// Prepare a temporary Codex home and a separate root with test files.
let codex_home = TempDir::new().context("create temp codex home")?;
let root = TempDir::new().context("create temp search root")?;
let codex_home = TempDir::new()?;
let root = TempDir::new()?;
// Create files designed to have deterministic ordering for query "abe".
std::fs::write(root.path().join("abc"), "x").context("write file abc")?;
std::fs::write(root.path().join("abcde"), "x").context("write file abcde")?;
std::fs::write(root.path().join("abexy"), "x").context("write file abexy")?;
std::fs::write(root.path().join("zzz.txt"), "x").context("write file zzz")?;
std::fs::write(root.path().join("abc"), "x")?;
std::fs::write(root.path().join("abcde"), "x")?;
std::fs::write(root.path().join("abexy"), "x")?;
std::fs::write(root.path().join("zzz.txt"), "x")?;
let sub_dir = root.path().join("sub");
std::fs::create_dir_all(&sub_dir).context("create sub dir")?;
std::fs::create_dir_all(&sub_dir)?;
let sub_abce_path = sub_dir.join("abce");
std::fs::write(&sub_abce_path, "x").context("write file sub/abce")?;
std::fs::write(&sub_abce_path, "x")?;
let sub_abce_rel = sub_abce_path
.strip_prefix(root.path())
.context("strip root prefix from sub/abce")?
.strip_prefix(root.path())?
.to_string_lossy()
.to_string();
// Start MCP server and initialize.
let mut mcp = McpProcess::new(codex_home.path())
.await
.context("spawn mcp")?;
timeout(DEFAULT_READ_TIMEOUT, mcp.initialize())
.await
.context("init timeout")?
.context("init failed")?;
let mut mcp = McpProcess::new(codex_home.path()).await?;
timeout(DEFAULT_READ_TIMEOUT, mcp.initialize()).await??;
let root_path = root.path().to_string_lossy().to_string();
// Send fuzzyFileSearch request.
let request_id = mcp
.send_fuzzy_file_search_request("abe", vec![root_path.clone()], None)
.await
.context("send fuzzyFileSearch")?;
.await?;
// Read response and verify shape and ordering.
let resp: JSONRPCResponse = timeout(
DEFAULT_READ_TIMEOUT,
mcp.read_stream_until_response_message(RequestId::Integer(request_id)),
)
.await
.context("fuzzyFileSearch timeout")?
.context("fuzzyFileSearch resp")?;
.await??;
let value = resp.result;
// The path separator on Windows affects the score.
@@ -94,24 +85,18 @@ async fn test_fuzzy_file_search_sorts_and_includes_indices() -> Result<()> {
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
async fn test_fuzzy_file_search_accepts_cancellation_token() -> Result<()> {
let codex_home = TempDir::new().context("create temp codex home")?;
let root = TempDir::new().context("create temp search root")?;
let codex_home = TempDir::new()?;
let root = TempDir::new()?;
std::fs::write(root.path().join("alpha.txt"), "contents").context("write alpha")?;
std::fs::write(root.path().join("alpha.txt"), "contents")?;
let mut mcp = McpProcess::new(codex_home.path())
.await
.context("spawn mcp")?;
timeout(DEFAULT_READ_TIMEOUT, mcp.initialize())
.await
.context("init timeout")?
.context("init failed")?;
let mut mcp = McpProcess::new(codex_home.path()).await?;
timeout(DEFAULT_READ_TIMEOUT, mcp.initialize()).await??;
let root_path = root.path().to_string_lossy().to_string();
let request_id = mcp
.send_fuzzy_file_search_request("alp", vec![root_path.clone()], None)
.await
.context("send fuzzyFileSearch")?;
.await?;
let request_id_2 = mcp
.send_fuzzy_file_search_request(
@@ -119,23 +104,20 @@ async fn test_fuzzy_file_search_accepts_cancellation_token() -> Result<()> {
vec![root_path.clone()],
Some(request_id.to_string()),
)
.await
.context("send fuzzyFileSearch")?;
.await?;
let resp: JSONRPCResponse = timeout(
DEFAULT_READ_TIMEOUT,
mcp.read_stream_until_response_message(RequestId::Integer(request_id_2)),
)
.await
.context("fuzzyFileSearch timeout")?
.context("fuzzyFileSearch resp")?;
.await??;
let files = resp
.result
.get("files")
.context("files key missing")?
.ok_or_else(|| anyhow!("files key missing"))?
.as_array()
.context("files not array")?
.ok_or_else(|| anyhow!("files not array"))?
.clone();
assert_eq!(files.len(), 1);

View File

@@ -18,8 +18,8 @@ use tempfile::TempDir;
use tokio::time::timeout;
use app_test_support::McpProcess;
use app_test_support::create_mock_chat_completions_server;
use app_test_support::create_shell_sse_response;
use app_test_support::create_mock_responses_server_sequence;
use app_test_support::create_shell_command_sse_response;
use app_test_support::to_response;
const DEFAULT_READ_TIMEOUT: std::time::Duration = std::time::Duration::from_secs(10);
@@ -56,7 +56,7 @@ async fn shell_command_interruption() -> anyhow::Result<()> {
std::fs::create_dir(&working_directory)?;
// Create mock server with a single SSE response: the long sleep command
let server = create_mock_chat_completions_server(vec![create_shell_sse_response(
let server = create_mock_responses_server_sequence(vec![create_shell_command_sse_response(
shell_command.clone(),
Some(&working_directory),
Some(10_000), // 10 seconds timeout in ms
@@ -88,7 +88,10 @@ async fn shell_command_interruption() -> anyhow::Result<()> {
// 2) addConversationListener
let add_listener_id = mcp
.send_add_conversation_listener_request(AddConversationListenerParams { conversation_id })
.send_add_conversation_listener_request(AddConversationListenerParams {
conversation_id,
experimental_raw_events: false,
})
.await?;
let _add_listener_resp: JSONRPCResponse = timeout(
DEFAULT_READ_TIMEOUT,
@@ -143,14 +146,14 @@ fn create_config_toml(codex_home: &Path, server_uri: String) -> std::io::Result<
r#"
model = "mock-model"
approval_policy = "never"
sandbox_mode = "danger-full-access"
sandbox_mode = "read-only"
model_provider = "mock_provider"
[model_providers.mock_provider]
name = "Mock provider for test"
base_url = "{server_uri}/v1"
wire_api = "chat"
wire_api = "responses"
request_max_retries = 0
stream_max_retries = 0
"#

View File

@@ -1,79 +1,80 @@
use std::fs;
use std::path::Path;
use anyhow::Result;
use app_test_support::McpProcess;
use app_test_support::create_fake_rollout;
use app_test_support::to_response;
use codex_app_server_protocol::JSONRPCNotification;
use codex_app_server_protocol::JSONRPCResponse;
use codex_app_server_protocol::ListConversationsParams;
use codex_app_server_protocol::ListConversationsResponse;
use codex_app_server_protocol::NewConversationParams; // reused for overrides shape
use codex_app_server_protocol::NewConversationParams;
use codex_app_server_protocol::RequestId;
use codex_app_server_protocol::ResumeConversationParams;
use codex_app_server_protocol::ResumeConversationResponse;
use codex_app_server_protocol::ServerNotification;
use codex_app_server_protocol::SessionConfiguredNotification;
use codex_core::protocol::EventMsg;
use codex_protocol::models::ContentItem;
use codex_protocol::models::ResponseItem;
use pretty_assertions::assert_eq;
use serde_json::json;
use tempfile::TempDir;
use tokio::time::timeout;
use uuid::Uuid;
const DEFAULT_READ_TIMEOUT: std::time::Duration = std::time::Duration::from_secs(10);
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
async fn test_list_and_resume_conversations() {
async fn test_list_and_resume_conversations() -> Result<()> {
// Prepare a temporary CODEX_HOME with a few fake rollout files.
let codex_home = TempDir::new().expect("create temp dir");
let codex_home = TempDir::new()?;
create_fake_rollout(
codex_home.path(),
"2025-01-02T12-00-00",
"2025-01-02T12:00:00Z",
"Hello A",
);
Some("openai"),
None,
)?;
create_fake_rollout(
codex_home.path(),
"2025-01-01T13-00-00",
"2025-01-01T13:00:00Z",
"Hello B",
);
Some("openai"),
None,
)?;
create_fake_rollout(
codex_home.path(),
"2025-01-01T12-00-00",
"2025-01-01T12:00:00Z",
"Hello C",
);
None,
None,
)?;
let mut mcp = McpProcess::new(codex_home.path())
.await
.expect("spawn mcp process");
timeout(DEFAULT_READ_TIMEOUT, mcp.initialize())
.await
.expect("init timeout")
.expect("init failed");
let mut mcp = McpProcess::new(codex_home.path()).await?;
timeout(DEFAULT_READ_TIMEOUT, mcp.initialize()).await??;
// Request first page with size 2
let req_id = mcp
.send_list_conversations_request(ListConversationsParams {
page_size: Some(2),
cursor: None,
model_providers: None,
})
.await
.expect("send listConversations");
.await?;
let resp: JSONRPCResponse = timeout(
DEFAULT_READ_TIMEOUT,
mcp.read_stream_until_response_message(RequestId::Integer(req_id)),
)
.await
.expect("listConversations timeout")
.expect("listConversations resp");
.await??;
let ListConversationsResponse { items, next_cursor } =
to_response::<ListConversationsResponse>(resp).expect("deserialize response");
to_response::<ListConversationsResponse>(resp)?;
assert_eq!(items.len(), 2);
// Newest first; preview text should match
assert_eq!(items[0].preview, "Hello A");
assert_eq!(items[1].preview, "Hello B");
assert_eq!(items[0].model_provider, "openai");
assert_eq!(items[1].model_provider, "openai");
assert!(items[0].path.is_absolute());
assert!(next_cursor.is_some());
@@ -82,129 +83,356 @@ async fn test_list_and_resume_conversations() {
.send_list_conversations_request(ListConversationsParams {
page_size: Some(2),
cursor: next_cursor,
model_providers: None,
})
.await
.expect("send listConversations page 2");
.await?;
let resp2: JSONRPCResponse = timeout(
DEFAULT_READ_TIMEOUT,
mcp.read_stream_until_response_message(RequestId::Integer(req_id2)),
)
.await
.expect("listConversations page 2 timeout")
.expect("listConversations page 2 resp");
.await??;
let ListConversationsResponse {
items: items2,
next_cursor: next2,
..
} = to_response::<ListConversationsResponse>(resp2).expect("deserialize response");
} = to_response::<ListConversationsResponse>(resp2)?;
assert_eq!(items2.len(), 1);
assert_eq!(items2[0].preview, "Hello C");
assert!(next2.is_some());
assert_eq!(items2[0].model_provider, "openai");
assert_eq!(next2, None);
// Now resume one of the sessions and expect a SessionConfigured notification and response.
// Add a conversation with an explicit non-OpenAI provider for filter tests.
create_fake_rollout(
codex_home.path(),
"2025-01-01T11-30-00",
"2025-01-01T11:30:00Z",
"Hello TP",
Some("test-provider"),
None,
)?;
// Filtering by model provider should return only matching sessions.
let filter_req_id = mcp
.send_list_conversations_request(ListConversationsParams {
page_size: Some(10),
cursor: None,
model_providers: Some(vec!["test-provider".to_string()]),
})
.await?;
let filter_resp: JSONRPCResponse = timeout(
DEFAULT_READ_TIMEOUT,
mcp.read_stream_until_response_message(RequestId::Integer(filter_req_id)),
)
.await??;
let ListConversationsResponse {
items: filtered_items,
next_cursor: filtered_next,
} = to_response::<ListConversationsResponse>(filter_resp)?;
assert_eq!(filtered_items.len(), 1);
assert_eq!(filtered_next, None);
assert_eq!(filtered_items[0].preview, "Hello TP");
assert_eq!(filtered_items[0].model_provider, "test-provider");
// Empty filter should include every session regardless of provider metadata.
let unfiltered_req_id = mcp
.send_list_conversations_request(ListConversationsParams {
page_size: Some(10),
cursor: None,
model_providers: Some(Vec::new()),
})
.await?;
let unfiltered_resp: JSONRPCResponse = timeout(
DEFAULT_READ_TIMEOUT,
mcp.read_stream_until_response_message(RequestId::Integer(unfiltered_req_id)),
)
.await??;
let ListConversationsResponse {
items: unfiltered_items,
next_cursor: unfiltered_next,
} = to_response::<ListConversationsResponse>(unfiltered_resp)?;
assert_eq!(unfiltered_items.len(), 4);
assert!(unfiltered_next.is_none());
let empty_req_id = mcp
.send_list_conversations_request(ListConversationsParams {
page_size: Some(10),
cursor: None,
model_providers: Some(vec!["other".to_string()]),
})
.await?;
let empty_resp: JSONRPCResponse = timeout(
DEFAULT_READ_TIMEOUT,
mcp.read_stream_until_response_message(RequestId::Integer(empty_req_id)),
)
.await??;
let ListConversationsResponse {
items: empty_items,
next_cursor: empty_next,
} = to_response::<ListConversationsResponse>(empty_resp)?;
assert!(empty_items.is_empty());
assert!(empty_next.is_none());
let first_item = &items[0];
// Now resume one of the sessions from an explicit rollout path.
let resume_req_id = mcp
.send_resume_conversation_request(ResumeConversationParams {
path: items[0].path.clone(),
path: Some(first_item.path.clone()),
conversation_id: None,
history: None,
overrides: Some(NewConversationParams {
model: Some("o3".to_string()),
..Default::default()
}),
})
.await
.expect("send resumeConversation");
.await?;
// Expect a codex/event notification with msg.type == sessionConfigured
let notification: JSONRPCNotification = timeout(
DEFAULT_READ_TIMEOUT,
mcp.read_stream_until_notification_message("sessionConfigured"),
)
.await
.expect("sessionConfigured notification timeout")
.expect("sessionConfigured notification");
let session_configured: ServerNotification = notification
.try_into()
.expect("deserialize sessionConfigured notification");
// Basic shape assertion: ensure event type is sessionConfigured
.await??;
let session_configured: ServerNotification = notification.try_into()?;
let ServerNotification::SessionConfigured(SessionConfiguredNotification {
model,
rollout_path,
initial_messages: session_initial_messages,
..
}) = session_configured
else {
unreachable!("expected sessionConfigured notification");
};
assert_eq!(model, "o3");
assert_eq!(items[0].path.clone(), rollout_path);
assert_eq!(rollout_path, first_item.path.clone());
let session_initial_messages = session_initial_messages
.expect("expected initial messages when resuming from rollout path");
match session_initial_messages.as_slice() {
[EventMsg::UserMessage(message)] => {
assert_eq!(message.message, first_item.preview.clone());
}
other => panic!("unexpected initial messages from rollout resume: {other:#?}"),
}
// Then the response for resumeConversation
let resume_resp: JSONRPCResponse = timeout(
DEFAULT_READ_TIMEOUT,
mcp.read_stream_until_response_message(RequestId::Integer(resume_req_id)),
)
.await
.expect("resumeConversation timeout")
.expect("resumeConversation resp");
.await??;
let ResumeConversationResponse {
conversation_id, ..
} = to_response::<ResumeConversationResponse>(resume_resp)
.expect("deserialize resumeConversation response");
conversation_id,
model: resume_model,
initial_messages: response_initial_messages,
..
} = to_response::<ResumeConversationResponse>(resume_resp)?;
// conversation id should be a valid UUID
assert!(!conversation_id.to_string().is_empty());
assert_eq!(resume_model, "o3");
let response_initial_messages =
response_initial_messages.expect("expected initial messages in resume response");
match response_initial_messages.as_slice() {
[EventMsg::UserMessage(message)] => {
assert_eq!(message.message, first_item.preview.clone());
}
other => panic!("unexpected initial messages in resume response: {other:#?}"),
}
// Resuming with only a conversation id should locate the rollout automatically.
let resume_by_id_req_id = mcp
.send_resume_conversation_request(ResumeConversationParams {
path: None,
conversation_id: Some(first_item.conversation_id),
history: None,
overrides: Some(NewConversationParams {
model: Some("o3".to_string()),
..Default::default()
}),
})
.await?;
let notification: JSONRPCNotification = timeout(
DEFAULT_READ_TIMEOUT,
mcp.read_stream_until_notification_message("sessionConfigured"),
)
.await??;
let session_configured: ServerNotification = notification.try_into()?;
let ServerNotification::SessionConfigured(SessionConfiguredNotification {
model,
rollout_path,
initial_messages: session_initial_messages,
..
}) = session_configured
else {
unreachable!("expected sessionConfigured notification");
};
assert_eq!(model, "o3");
assert_eq!(rollout_path, first_item.path.clone());
let session_initial_messages = session_initial_messages
.expect("expected initial messages when resuming from conversation id");
match session_initial_messages.as_slice() {
[EventMsg::UserMessage(message)] => {
assert_eq!(message.message, first_item.preview.clone());
}
other => panic!("unexpected initial messages from conversation id resume: {other:#?}"),
}
let resume_resp: JSONRPCResponse = timeout(
DEFAULT_READ_TIMEOUT,
mcp.read_stream_until_response_message(RequestId::Integer(resume_by_id_req_id)),
)
.await??;
let ResumeConversationResponse {
conversation_id: by_id_conversation_id,
model: by_id_model,
initial_messages: by_id_initial_messages,
..
} = to_response::<ResumeConversationResponse>(resume_resp)?;
assert!(!by_id_conversation_id.to_string().is_empty());
assert_eq!(by_id_model, "o3");
let by_id_initial_messages = by_id_initial_messages
.expect("expected initial messages when resuming from conversation id response");
match by_id_initial_messages.as_slice() {
[EventMsg::UserMessage(message)] => {
assert_eq!(message.message, first_item.preview.clone());
}
other => {
panic!("unexpected initial messages in conversation id resume response: {other:#?}")
}
}
// Resuming with explicit history should succeed even without a stored rollout.
let fork_history_text = "Hello from history";
let history = vec![ResponseItem::Message {
id: None,
role: "user".to_string(),
content: vec![ContentItem::InputText {
text: fork_history_text.to_string(),
}],
}];
let resume_with_history_req_id = mcp
.send_resume_conversation_request(ResumeConversationParams {
path: None,
conversation_id: None,
history: Some(history),
overrides: Some(NewConversationParams {
model: Some("o3".to_string()),
..Default::default()
}),
})
.await?;
let notification: JSONRPCNotification = timeout(
DEFAULT_READ_TIMEOUT,
mcp.read_stream_until_notification_message("sessionConfigured"),
)
.await??;
let session_configured: ServerNotification = notification.try_into()?;
let ServerNotification::SessionConfigured(SessionConfiguredNotification {
model,
initial_messages: session_initial_messages,
..
}) = session_configured
else {
unreachable!("expected sessionConfigured notification");
};
assert_eq!(model, "o3");
assert!(
session_initial_messages.as_ref().is_none_or(Vec::is_empty),
"expected no initial messages when resuming from explicit history but got {session_initial_messages:#?}"
);
let resume_resp: JSONRPCResponse = timeout(
DEFAULT_READ_TIMEOUT,
mcp.read_stream_until_response_message(RequestId::Integer(resume_with_history_req_id)),
)
.await??;
let ResumeConversationResponse {
conversation_id: history_conversation_id,
model: history_model,
initial_messages: history_initial_messages,
..
} = to_response::<ResumeConversationResponse>(resume_resp)?;
assert!(!history_conversation_id.to_string().is_empty());
assert_eq!(history_model, "o3");
assert!(
history_initial_messages.as_ref().is_none_or(Vec::is_empty),
"expected no initial messages in resume response when history is provided but got {history_initial_messages:#?}"
);
Ok(())
}
fn create_fake_rollout(codex_home: &Path, filename_ts: &str, meta_rfc3339: &str, preview: &str) {
let uuid = Uuid::new_v4();
// sessions/YYYY/MM/DD/ derived from filename_ts (YYYY-MM-DDThh-mm-ss)
let year = &filename_ts[0..4];
let month = &filename_ts[5..7];
let day = &filename_ts[8..10];
let dir = codex_home.join("sessions").join(year).join(month).join(day);
fs::create_dir_all(&dir).unwrap_or_else(|e| panic!("create sessions dir: {e}"));
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
async fn list_conversations_fetches_through_filtered_pages() -> Result<()> {
let codex_home = TempDir::new()?;
let file_path = dir.join(format!("rollout-{filename_ts}-{uuid}.jsonl"));
let mut lines = Vec::new();
// Meta line with timestamp (flattened meta in payload for new schema)
lines.push(
json!({
"timestamp": meta_rfc3339,
"type": "session_meta",
"payload": {
"id": uuid,
"timestamp": meta_rfc3339,
"cwd": "/",
"originator": "codex",
"cli_version": "0.0.0",
"instructions": null
}
// Only the last 3 conversations match the provider filter; request 3 and
// ensure pagination keeps fetching past non-matching pages.
let cases = [
(
"2025-03-04T12-00-00",
"2025-03-04T12:00:00Z",
"skip_provider",
),
(
"2025-03-03T12-00-00",
"2025-03-03T12:00:00Z",
"skip_provider",
),
(
"2025-03-02T12-00-00",
"2025-03-02T12:00:00Z",
"target_provider",
),
(
"2025-03-01T12-00-00",
"2025-03-01T12:00:00Z",
"target_provider",
),
(
"2025-02-28T12-00-00",
"2025-02-28T12:00:00Z",
"target_provider",
),
];
for (ts_file, ts_rfc, provider) in cases {
create_fake_rollout(
codex_home.path(),
ts_file,
ts_rfc,
"Hello",
Some(provider),
None,
)?;
}
let mut mcp = McpProcess::new(codex_home.path()).await?;
timeout(DEFAULT_READ_TIMEOUT, mcp.initialize()).await??;
let req_id = mcp
.send_list_conversations_request(ListConversationsParams {
page_size: Some(3),
cursor: None,
model_providers: Some(vec!["target_provider".to_string()]),
})
.to_string(),
.await?;
let resp: JSONRPCResponse = timeout(
DEFAULT_READ_TIMEOUT,
mcp.read_stream_until_response_message(RequestId::Integer(req_id)),
)
.await??;
let ListConversationsResponse { items, next_cursor } =
to_response::<ListConversationsResponse>(resp)?;
assert_eq!(
items.len(),
3,
"should fetch across pages to satisfy the limit"
);
// Minimal user message entry as a persisted response item (with envelope timestamp)
lines.push(
json!({
"timestamp": meta_rfc3339,
"type":"response_item",
"payload": {
"type":"message",
"role":"user",
"content":[{"type":"input_text","text": preview}]
}
})
.to_string(),
assert!(
items
.iter()
.all(|item| item.model_provider == "target_provider")
);
// Add a matching user message event line to satisfy filters
lines.push(
json!({
"timestamp": meta_rfc3339,
"type":"event_msg",
"payload": {
"type":"user_message",
"message": preview,
"kind": "plain"
}
})
.to_string(),
);
fs::write(file_path, lines.join("\n") + "\n")
.unwrap_or_else(|e| panic!("write rollout file: {e}"));
assert_eq!(next_cursor, None);
Ok(())
}

View File

@@ -1,10 +1,6 @@
use std::path::Path;
use std::time::Duration;
use anyhow::Result;
use app_test_support::McpProcess;
use app_test_support::to_response;
use codex_app_server_protocol::CancelLoginChatGptParams;
use codex_app_server_protocol::CancelLoginChatGptResponse;
use codex_app_server_protocol::GetAuthStatusParams;
use codex_app_server_protocol::GetAuthStatusResponse;
use codex_app_server_protocol::JSONRPCError;
@@ -12,7 +8,10 @@ use codex_app_server_protocol::JSONRPCResponse;
use codex_app_server_protocol::LoginChatGptResponse;
use codex_app_server_protocol::LogoutChatGptResponse;
use codex_app_server_protocol::RequestId;
use codex_core::auth::AuthCredentialsStoreMode;
use codex_login::login_with_api_key;
use serial_test::serial;
use std::path::Path;
use tempfile::TempDir;
use tokio::time::timeout;
@@ -33,7 +32,7 @@ model_provider = "mock_provider"
[model_providers.mock_provider]
name = "Mock provider for test"
base_url = "http://127.0.0.1:0/v1"
wire_api = "chat"
wire_api = "responses"
request_max_retries = 0
stream_max_retries = 0
"#,
@@ -41,32 +40,26 @@ stream_max_retries = 0
}
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
async fn logout_chatgpt_removes_auth() {
let codex_home = TempDir::new().unwrap_or_else(|e| panic!("create tempdir: {e}"));
create_config_toml(codex_home.path()).expect("write config.toml");
login_with_api_key(codex_home.path(), "sk-test-key").expect("seed api key");
async fn logout_chatgpt_removes_auth() -> Result<()> {
let codex_home = TempDir::new()?;
create_config_toml(codex_home.path())?;
login_with_api_key(
codex_home.path(),
"sk-test-key",
AuthCredentialsStoreMode::File,
)?;
assert!(codex_home.path().join("auth.json").exists());
let mut mcp = McpProcess::new_with_env(codex_home.path(), &[("OPENAI_API_KEY", None)])
.await
.expect("spawn mcp process");
timeout(DEFAULT_READ_TIMEOUT, mcp.initialize())
.await
.expect("init timeout")
.expect("init failed");
let mut mcp = McpProcess::new_with_env(codex_home.path(), &[("OPENAI_API_KEY", None)]).await?;
timeout(DEFAULT_READ_TIMEOUT, mcp.initialize()).await??;
let id = mcp
.send_logout_chat_gpt_request()
.await
.expect("send logoutChatGpt");
let id = mcp.send_logout_chat_gpt_request().await?;
let resp: JSONRPCResponse = timeout(
DEFAULT_READ_TIMEOUT,
mcp.read_stream_until_response_message(RequestId::Integer(id)),
)
.await
.expect("logoutChatGpt timeout")
.expect("logoutChatGpt response");
let _ok: LogoutChatGptResponse = to_response(resp).expect("deserialize logout response");
.await??;
let _ok: LogoutChatGptResponse = to_response(resp)?;
assert!(
!codex_home.path().join("auth.json").exists(),
@@ -79,71 +72,16 @@ async fn logout_chatgpt_removes_auth() {
include_token: Some(true),
refresh_token: Some(false),
})
.await
.expect("send getAuthStatus");
.await?;
let status_resp: JSONRPCResponse = timeout(
DEFAULT_READ_TIMEOUT,
mcp.read_stream_until_response_message(RequestId::Integer(status_id)),
)
.await
.expect("getAuthStatus timeout")
.expect("getAuthStatus response");
let status: GetAuthStatusResponse = to_response(status_resp).expect("deserialize status");
.await??;
let status: GetAuthStatusResponse = to_response(status_resp)?;
assert_eq!(status.auth_method, None);
assert_eq!(status.auth_token, None);
}
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
async fn login_and_cancel_chatgpt() {
let codex_home = TempDir::new().unwrap_or_else(|e| panic!("create tempdir: {e}"));
create_config_toml(codex_home.path()).unwrap_or_else(|err| panic!("write config.toml: {err}"));
let mut mcp = McpProcess::new(codex_home.path())
.await
.expect("spawn mcp process");
timeout(DEFAULT_READ_TIMEOUT, mcp.initialize())
.await
.expect("init timeout")
.expect("init failed");
let login_id = mcp
.send_login_chat_gpt_request()
.await
.expect("send loginChatGpt");
let login_resp: JSONRPCResponse = timeout(
DEFAULT_READ_TIMEOUT,
mcp.read_stream_until_response_message(RequestId::Integer(login_id)),
)
.await
.expect("loginChatGpt timeout")
.expect("loginChatGpt response");
let login: LoginChatGptResponse = to_response(login_resp).expect("deserialize login resp");
let cancel_id = mcp
.send_cancel_login_chat_gpt_request(CancelLoginChatGptParams {
login_id: login.login_id,
})
.await
.expect("send cancelLoginChatGpt");
let cancel_resp: JSONRPCResponse = timeout(
DEFAULT_READ_TIMEOUT,
mcp.read_stream_until_response_message(RequestId::Integer(cancel_id)),
)
.await
.expect("cancelLoginChatGpt timeout")
.expect("cancelLoginChatGpt response");
let _ok: CancelLoginChatGptResponse =
to_response(cancel_resp).expect("deserialize cancel response");
// Optionally observe the completion notification; do not fail if it races.
let maybe_note = timeout(
Duration::from_secs(2),
mcp.read_stream_until_notification_message("codex/event/login_chat_gpt_complete"),
)
.await;
if maybe_note.is_err() {
eprintln!("warning: did not observe login_chat_gpt_complete notification after cancel");
}
Ok(())
}
fn create_config_toml_forced_login(codex_home: &Path, forced_method: &str) -> std::io::Result<()> {
@@ -176,66 +114,48 @@ forced_chatgpt_workspace_id = "{workspace_id}"
}
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
async fn login_chatgpt_rejected_when_forced_api() {
let codex_home = TempDir::new().unwrap_or_else(|e| panic!("create tempdir: {e}"));
create_config_toml_forced_login(codex_home.path(), "api")
.unwrap_or_else(|err| panic!("write config.toml: {err}"));
async fn login_chatgpt_rejected_when_forced_api() -> Result<()> {
let codex_home = TempDir::new()?;
create_config_toml_forced_login(codex_home.path(), "api")?;
let mut mcp = McpProcess::new(codex_home.path())
.await
.expect("spawn mcp process");
timeout(DEFAULT_READ_TIMEOUT, mcp.initialize())
.await
.expect("init timeout")
.expect("init failed");
let mut mcp = McpProcess::new(codex_home.path()).await?;
timeout(DEFAULT_READ_TIMEOUT, mcp.initialize()).await??;
let request_id = mcp
.send_login_chat_gpt_request()
.await
.expect("send loginChatGpt");
let request_id = mcp.send_login_chat_gpt_request().await?;
let err: JSONRPCError = timeout(
DEFAULT_READ_TIMEOUT,
mcp.read_stream_until_error_message(RequestId::Integer(request_id)),
)
.await
.expect("loginChatGpt error timeout")
.expect("loginChatGpt error");
.await??;
assert_eq!(
err.error.message,
"ChatGPT login is disabled. Use API key login instead."
);
Ok(())
}
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
async fn login_chatgpt_includes_forced_workspace_query_param() {
let codex_home = TempDir::new().unwrap_or_else(|e| panic!("create tempdir: {e}"));
create_config_toml_forced_workspace(codex_home.path(), "ws-forced")
.unwrap_or_else(|err| panic!("write config.toml: {err}"));
// Serialize tests that launch the login server since it binds to a fixed port.
#[serial(login_port)]
async fn login_chatgpt_includes_forced_workspace_query_param() -> Result<()> {
let codex_home = TempDir::new()?;
create_config_toml_forced_workspace(codex_home.path(), "ws-forced")?;
let mut mcp = McpProcess::new(codex_home.path())
.await
.expect("spawn mcp process");
timeout(DEFAULT_READ_TIMEOUT, mcp.initialize())
.await
.expect("init timeout")
.expect("init failed");
let mut mcp = McpProcess::new(codex_home.path()).await?;
timeout(DEFAULT_READ_TIMEOUT, mcp.initialize()).await??;
let request_id = mcp
.send_login_chat_gpt_request()
.await
.expect("send loginChatGpt");
let request_id = mcp.send_login_chat_gpt_request().await?;
let resp: JSONRPCResponse = timeout(
DEFAULT_READ_TIMEOUT,
mcp.read_stream_until_response_message(RequestId::Integer(request_id)),
)
.await
.expect("loginChatGpt timeout")
.expect("loginChatGpt response");
.await??;
let login: LoginChatGptResponse = to_response(resp).expect("deserialize login resp");
let login: LoginChatGptResponse = to_response(resp)?;
assert!(
login.auth_url.contains("allowed_workspace_id=ws-forced"),
"auth URL should include forced workspace"
);
Ok(())
}

Some files were not shown because too many files have changed in this diff Show More