## Why
It should be possible for app-server to handle "foreign" OS paths in
unified_exec working directories, allowing e.g. a Linux app-server to
run processes on e.g. a Windows exec-server.
## What
Convert the core unified_exec cwd values to use `PathUri`.
Adds fallible path conversion in several places to try to minimize the
scope of this change. The only time this change suppresses errors from
converting `PathUri` to an `AbsolutePathBuf` is when the turn is
configured with no sandboxing at all to allow us to make progress
testing without sandboxing.
Future changes to apply_patch and sandboxing will clean up these error
paths.
A tool's cwd is resolved from joining a model-provided workdir to the
environment's cwd. When using `AbsolutePathBuf::join()`, an
absolute-path workdir would overwrite the environment's cwd and we would
resolve permissions/sandboxing against the model-provided path. This
change extends `PathUri::join()` to also treat an absolute rhs as an
override of the base/lhs.
This also removes some coverage from the remove_env_windows tests until
a follow-up converts foreign paths in command exec events correctly.
## Breaking Changes
When using `AbsolutePathBuf::join()` for workdir resolution, we ended up
resolving tilde-prefixed paths against the app-server's `$HOME`, e.g.
`~/foo/bar` becomes `/home/anp/foo/bar`. It's difficult to do this with
`PathUri` joining, so after offline discussion this PR no longer
implements it.
A quick check of some power users' rollouts suggests that models don't
actually generate home-prefixed absolute working directories for their
spawns, so this shouldn't have any real blast radius.
## Why
#28152 jumped the gun on moving the rollout format to store URIs, and
would likely break compat with some features that don't go through the
same types as the core logic.
## What
Make `TurnContextItem.cwd` an `AbsolutePathBuf` again, remove test added
for `PathUri` serialization in rollouts. Also drops a bunch of error
paths that are no longer needed.
## Why
Model-visible `<environment_context>` should match the environment of
the executor, not of the app server.
Stacked on #28146.
## What
- Keep selected environment cwd values as `PathUri` while building
environment context.
- Render cwd text using the path convention represented by the URI, with
the canonical URI as a fallback.
- Preserve compatibility with legacy `TurnContextItem.cwd` values when
reconstructing and diffing context.
- Extend the Wine-backed remote Windows test to assert that the model
sees `powershell` and `C:\windows`.
## Why
app-server may run on a different OS from the selected exec-server
environment. Parsing that environment’s cwd with the Codex host’s path
rules prevents thread startup.
## What
Carry environment cwd values as `LegacyAppPathString` at the app-server
boundary and `PathUri` internally. Existing tool-call schemas and
relative-path behavior stay host-native; remaining local-only consumers
convert explicitly and leave follow-up TODOs.
The Wine integration test verifies app-server can start a thread and
complete an ordinary turn with a Windows environment cwd from Linux.
## Validation
- `bazel test //codex-rs/core/tests/remote_env_windows:smoke-test
--test_output=errors`
- focused app-server environment-selection and protocol schema tests
- scoped Clippy for `codex-core` and `codex-app-server-protocol`
## Why
Help Codex understand how to execute the migration to support cross-OS
paths.
## What
Expand the path-types skill with our goals and constraints.
## Why
`ApiPathString` kind of implies that it can be used anywhere we pull a
path out of JSON, but it's not really appropriate for tool arguments
when the model might generate relative paths.
Prefer `String` for model-generated paths and we can handle the
conversion per feature for now and define a shared abstraction later if
it makes sense.
# What
Rename `ApiPathString` to `AppLegacyPathString` to clarify its role.
Expand the `path-types` skill to tell the model to leave tool args as
bare strings.
## Why
Review caught some would-be regressions in changes to unified_exec that
weren't surfaced in CI.
## What
Add coverage for requesting permissions through unified exec when there
are additional permissions. Previously this flow was only tested against
shell_command.
## Why
In #28146 I missed the invariant that an absolute `exec_command` workdir
must override the environment cwd. The existing integration test would
have caught that regression, but it was ignored as flaky.
## What
Re-enable `unified_exec_respects_workdir_override`.
## Validation
`just test -p codex-core unified_exec_respects_workdir_override`
## Why
Ensure a consistent string format when exposing path conversion errors
to the model.
## What
- Render `PathUriParseError::InvalidFileUriPath` as `'$PATH' is invalid
on '$OS'`.
## Why
Clients running an app-server on one OS and an exec-server on another OS
need to be able to pass sandbox config to app-server that refers to
resources on the executor's foreign OS.
## What
`AbsolutePathBuf` can't represent these paths and we don't want users to
be exposed to `PathUri` yet, so this moves the public app-server API to
be expressed in terms of `ApiPathString`.
Stacked on #28165.
- change app-server v2 filesystem permission paths, including legacy
read/write roots, to `ApiPathString`
- localize API paths through `PathUri` when converting into the current
native core permission types
- make path-bearing permission conversions fallible and surface
localization failures instead of silently treating malformed grants as
ordinary denials
- propagate conversion failures through app-server and TUI approval
handling
- regenerate the app-server JSON and TypeScript schemas
- leave migration TODOs on native-path conversions so they can be
removed once core permission paths use `PathUri`
## Why
We want to exercise a linux app-server against a windows exec-server
without having to repeat every test case. This approach has slight
precedent in the remote docker test setup.
## What
Run the shared `codex-core` integration suite against Windows
exec-server behavior from Linux. This makes cross-OS path and shell
regressions visible while keeping unsupported cases owned by individual
tests.
- Add `local`, `docker`, and `wine-exec` test environment selection with
legacy Docker compatibility.
- Extend `codex_rust_crate` to generate a sharded Wine-exec variant
using a cross-built Windows server and pinned Bazel Wine/PowerShell
runtimes.
- Teach remote-aware helpers about Windows paths and track temporary
incompatibilities with source-local `skip_if_wine_exec!` calls and
follow-up reasons.
## Why
Progress towards letting app-server and exec-server run on different
platforms, specifically for sandbox configuration.
## What
- Make the filesystem path containment hierarchy generic, defaulting to
`AbsolutePathBuf` for now.
- Have clients specify `AbsolutePathBuf` or `PathUri` directly where
needed.
- Use `PathUri` throughout exec-server filesystem protocol and trait
boundaries.
- Implement `From` for conversion to path URIs and `TryFrom` for
fallible conversion to absolute paths through the generic type
hierarchy.
## Why
Codex contributors and agents need repository-scoped guidance for
choosing compatible Rust types
for operating system paths during the ongoing URI migration. Keeping the
guidance in the repository
makes the app-server and exec-server rules available consistently
without relying on a personal
skill installation.
## What
- Add the `path-types` skill at `.codex/skills/path-types/SKILL.md`.
- Document the intended uses of `ApiPathString`, `PathUri`,
`AbsolutePathBuf`, and `PathBuf` across
protocol, internal, and shared dependency boundaries.
- Keep migrations of existing types limited to explicit requests and
proportional edits.
## Validation
- Validated the skill structure with skill-creator's
`quick_validate.py`.
## Why
Next slice needed to make progress on the `remote_env_windows` test is
to support passing a Windows cwd for the remote environment and using
that environment's native shell. This lets the test run a real Windows
process instead of only recording an early path or shell mismatch.
## What
- change `TurnEnvironmentSelection.cwd` from `AbsolutePathBuf` to
`PathUri`
- convert local cwd values to URIs when constructing selections
- preserve a remote primary cwd instead of replacing it with the local
legacy fallback
- prefer the selected environment's discovered shell for unified exec,
falling back to the session shell when unavailable
- convert back to a host-native absolute path at current native-only
consumer boundaries
- reject or deny unsupported foreign cwd values at the existing
request-permissions boundary, with TODOs for its future migration
- extend the hermetic Wine test to execute Windows PowerShell in
`C:\windows` and verify successful process completion
- record the current app-server rejection against the same Wine-backed
remote Windows fixture when its cwd is supplied as a native Windows path
## Why
We're moving to `PathUri` in more places to support cross-OS
app-server/exec-server, but we don't want to expose the URI encoding to
users of app-server's public APIs yet.
We'll need to translate at the app-server API boundary between
client-visible "regular" paths that are appropriate for the OS of the
environment for which the paths make sense, which means using the
environment's path personality to do the conversion.
`PathUri` doesn't yet attempt to encode environment ID, so for now we'll
sniff the most likely path convention for a given path.
## What
- Add `PathConvention` and `NativePathString` with host-independent
POSIX, Windows drive, and UNC rendering.
- Cover cross-host rendering, encoding, Unicode, invalid components.
## Why
Cross-OS tests in the wine environment will be much more faithful if we
can also test powershell integration.
## What
Add an x86_64 powershell binary to the bazel wine environment and
include smoke tests.
## Intent
Keep Bazel and Starlark files consistently formatted without requiring
contributors to install or version buildifier themselves.
## Implementation
- Add a SHA-256-pinned, cross-platform DotSlash manifest for buildifier
v8.5.1.
- Run buildifier from the shared `just fmt` and `just fmt-check` driver,
with Windows-safe explicit DotSlash invocation.
- Provision DotSlash in formatting CI and contributor devcontainers, and
document the source-build prerequisite.
- Apply the initial mechanical buildifier formatting baseline.
## Why
This is the second-to-last place in the exec-server protocol that needs
to migrate to URIs to support cross-OS operation.
## What
- Change `ExecParams.cwd` to `PathUri`.
- Keep the cwd URI-shaped through core and rmcp producers, converting it
to `AbsolutePathBuf` only in `LocalProcess::start_process`.
- Reject non-native cwd URIs before launch and update the affected
protocol documentation and call sites.
## Why
We want to make it possible for an app-server orchestrator on one OS to
control an exec-server on another host running a different OS. In
practice this kinda already works if you get lucky and the two hosts
have the same path format, but we mangle quite a lot of operations if
either end is Windows.
This test starts exercising that interaction, although right now the
initial bootstrap fails. Future changes will expand the test's
assertions to match improved support.
## What
Stacked on #27964. This adds a small Windows exec-server fixture and a
Linux protocol smoke test using the reusable Wine harness, covering
Windows environment discovery, non-TTY `cmd.exe` execution, output, exit
status, and working directory.
Once we've got the full codex binary cross-building under Bazel we could
consider moving to the real binary instead of the stripped down
exec-server-only binary used here.
## Why
We want to make it possible for an app-server orchestrator on one OS to
control an exec-server on another host running a different OS. In
practice this kinda already works if you get lucky and the two hosts
have the same path format, but we mangle quite a lot of operations if
either end is Windows.
We should be able to test the cross-platform interactions for
exec-server, but we want to do this fairly soon and need a lightweight
option for testing. Using Wine to run the Windows side is far from
perfect, but it should give us a decent measure of how well we're
handling the basics of paths, process spawning, shell interaction, etc.
Future changes will add actual exec-server tests and possibly extensions
to the Wine testing environment.
## What
To make the cross-target-triple build easy, these tests are added only
to the Bazel build. This change adds an x86_64 Wine prebuilt managed by
Bazel and some build rules that can set up the needed toolchain
transition.
The support library for running Wine in a test environment created by
the Bazel rules comes with its own basic unit and integration tests.
Their primary priority is to make sure we don't leak child processes on
developer machines and that we can build and launch a basic hello world
binary.
## Validation
Confirmed these new tests are running on the [x86_64 bazel ubuntu
jobs](https://github.com/openai/codex/actions/runs/27446432302/job/81132356855?pr=27937):
```
//bazel/rules/testing/wine:wine-smoke-test (cached) PASSED in 3.7s
//bazel/rules/testing/wine:wine-test-support-unit-tests (cached) PASSED in 15.8s
```
## Why
`PathUri::from_abs_path` can fail for absolute paths that do not have a
normal `file:` URI representation, forcing filesystem call sites to
handle a conversion error even though the original path can be preserved
losslessly.
## What
Make `from_abs_path` infallible and migrate its callers. Unrepresentable
paths use `file:///%00/bad/path/<base64>`, encoding Unix bytes or
Windows UTF-16LE; `to_abs_path` validates and decodes that fallback. The
leading encoded null reserves a namespace that cannot collide with a
real Unix or Windows path, and fallback URIs remain opaque to lexical
path operations.
## Validation
Added path-URI coverage for Unix null and non-UTF-8 paths, Windows
device/verbatim and non-Unicode paths, serialization, malformed
fallbacks, opaque lexical operations, invalid native payloads, and
literal `/bad/path` collision resistance.
Codex seems to do interesting things with `cfg`'s sometimes and it seems
it would be good to give it guidance about how broadly our Rust needs to
work.
This adds a very brief section to AGENTS.md explaining that we target
the major desktop OSes and that we want the vast majority of our logic
to be portable across them.
## Why
Sandbox cwd values can cross app-server and exec-server host boundaries.
They should retain URI semantics until the receiving host validates them
instead of being interpreted early as native paths.
## What
- Carry `PathUri` through filesystem sandbox contexts, sandbox commands,
and transform inputs.
- Convert command and policy cwd once in `SandboxManager::transform`,
then keep launch requests native.
- Preserve sandbox cwd over remote filesystem transport and reject
non-native URIs without fallback.
- Cache paired native/URI turn-environment cwd values during migration,
with immutable access to keep them synchronized.
- Extend existing protocol, forwarding, transform, and core runtime
tests.
## Why
We already have the machinery to support multiple environments on a
single thread, but we only show the model the contents of `AGENTS.md`
files in the primary environment.
We should show the model all of the relevant project instructions when
we know there's more than one environment.
## Known Gaps
As discussed in the RFC, this implementation:
1. doesn't handle environments being added/removed to/from the thread
after its creation
2. it doesn't enforce an aggregate context budget across environments,
and instead applies the configured project maximum independently to each
environment
## Implementation
- Discover project instructions in environment order with an independent
byte budget per environment and preserve source provenance/order.
- Keep the legacy fragment byte-for-byte when exactly one environment
contributes project instructions; use environment-labeled sections when
two or more environments contribute.
- Freeze the complete rendered fragment in `LoadedAgentsMd`, insert it
directly into requests, and recognize both layouts in contextual and
memory filtering.
- Add exact rendering, independent-budget, source-order,
creation-snapshot, and consumer coverage without changing app-server
schemas.
## Why
First-party async traits should expose their `Send` contracts explicitly
without requiring `async_trait`. This completes the migration pattern
established in #27303 and #27304.
## What changed
- Replaced the remaining first-party `async_trait` traits with native
return-position `impl Future + Send` where statically dispatched and
explicit boxed `Send` futures where object safety is required.
- Kept implementations behavior-preserving, outlining existing async
bodies into inherent methods where that keeps the diff reviewable.
- Removed all direct first-party `async-trait` dependencies and the
workspace dependency declaration.
- Added a cargo-deny policy that permits `async-trait` only through the
remaining transitive wrapper crates.
- Updated `rand` from 0.8.5 to 0.8.6 to resolve RUSTSEC-2026-0097 and
keep the full cargo-deny check passing.
## Validation
- `just test -p codex-exec-server`: 216 passed, 2 skipped.
- `just test -p codex-model-provider`: 39 passed.
- `just test -p codex-core` and `just test`: changed tests passed;
remaining failures are environment-sensitive suites unrelated to this
migration.
- `cargo deny check`
- `just fix`
- `just fmt`
- `cargo shear`
- `just bazel-lock-check`
## Why
`main` stopped compiling when #27498 passed an `AbsolutePathBuf` to the
`ExecutorFileSystem` API migrated to `PathUri` by #27653.
## What
Convert referenced image paths to `PathUri` before filesystem reads,
declare the internal path-URI dependency, and refresh `Cargo.lock`.
## Summary
Path composition is already handled by `PathUri`, leaving `fs/join` and
`fs/parent` as redundant exec-server protocol surface. Because
app-server and exec-server are deployed atomically, these obsolete
methods can be removed without a compatibility shim.
This removes the protocol constants and payloads, public client APIs,
server registrations and handlers, and endpoint-only tests. Existing
in-process `PathUri` join/parent coverage remains.
## Validation
- `just test -p codex-exec-server` (215 passed, 2 skipped)
Exec-server filesystem calls should preserve cross-platform `file:` URIs
across the remote boundary instead of converting them through paths
native to the client host.
This changes the exec-server filesystem protocol DTOs to use `PathUri`,
carries those values directly through remote and sandbox-helper
transports, and keeps legacy native absolute-path request strings
readable for compatibility. It also updates protocol documentation and
coverage for URI serialization and non-native URI forwarding.
## Why
We want to remove implicit use of `$CODEX_HOME` from `codex-core` and
make embedders responsible for supplying user-level instructions. This
also ensures user instructions load when no primary environment is
selected.
## What changed
Stacked on #27415, which makes `codex exec` surface thread-scoped
runtime warnings.
- Added `UserInstructionsProvider` to `codex-extension-api`, with
absolute source attribution and recoverable loading warnings.
- Added `codex-home` with the filesystem-backed provider for
`AGENTS.override.md` and `AGENTS.md`, preserving precedence, fallback,
trimming, lossy UTF-8 handling, and the existing uncapped global
instruction size.
- Removed global instruction loading from `Config` and require
`ThreadManager` callers to inject a provider.
- Load provider instructions once for each fresh root runtime, including
runtimes without a primary environment. Running sessions retain their
snapshot, while child agents inherit the parent snapshot without
invoking the provider.
- Keep provider instructions separate while loading project `AGENTS.md`,
then assemble the model-visible instructions with the existing ordering,
source attribution, warning, and turn-context behavior.
- Wired the Codex home provider through the CLI, app server, MCP server,
core facade, and thread-manager sample.
## Validation
- `just test -p codex-home -p codex-extension-api`
- `just test -p codex-core agents_md`
- `just test -p codex-core guardian`
- `just test -p codex-app-server
thread_start_without_selected_environment_includes_only_global_instruction_source`
- `just test -p codex-exec warning`
- `just bazel-lock-check`
## Why
We're moving exec-server to use PathUri for its internal path
representations.
## What
Move `ExecutorFileSystem` APIs to use `PathUri` instead of
`AbsolutePathBuf`. Future changes will convert higher-level parts of
exec-server.
We're switching to using a static encoding of the host path in
`PathUri`. We may need a type like this again but we can add it when
it's more compelling.
Stacked on #27454.
## Why
Windows ARM64 uses 64-bit `long double`, but the LLVM MinGW Bazel
configuration omits the upstream `powl` compatibility source and does
not link the `mingwex` archive that owns it. Cross-linking the release
binary therefore fails with an unresolved `powl` symbol.
## What changed
Patch the LLVM module to compile `math/arm-common/powl.c` into the ARM64
MinGW extension sources and add `-lmingwex` to the Windows toolchain
defaults.
## Validation
- `just bazel-lock-check`
Stack: 3 of 6. Depends on #27322.
## Why
`codex exec` drops thread-scoped warning notifications. Warnings
discovered while a thread starts, including unreadable or invalid UTF-8
project `AGENTS.md` files, therefore become silent.
## What changed
- Process global and primary-thread warning notifications while
continuing to ignore warnings from unrelated threads.
- Render runtime warnings in human output and expose them through the
existing non-fatal error item in JSONL output.
- Add focused routing, rendering, and malformed project-instruction
coverage.
## Why
The exec-server's existing filesystem tests only run on `#[cfg(unix)]`.
We should be running the applicable ones on Windows, and also include
the basic filesystem operations that will be modified by migrating to
`PathUri`.
## What
Split platform-neutral local/remote tests into a shared Unix/Windows
suite while keeping the existing `AbsolutePathBuf` API, and add Windows
junction canonicalization coverage.
Builder-style setters often repeat the setting name in both the method
and its sole argument. Calls such as `.enabled(false)` are already
self-documenting, so requiring `/*enabled*/` adds noise without
clarifying the call.
## What changed
- Exempt a method's sole non-self argument when its resolved parameter
name matches the method name.
- Continue validating any explicit argument comment against the resolved
parameter name.
- Continue requiring comments when method and parameter names differ or
when a method has multiple non-self arguments.
- Document the exception in `AGENTS.md` and the lint's own behavior
documentation.
## Examples
Before this change we'd need redundant comments like this:
```rust
builder.enabled(/*false*/ false);
builder.retry_count(/*retry_count*/ 3);
builder.base_url(/*base_url*/ None);
```
Now can be written like this:
```rust
builder.enabled(false);
builder.retry_count(3);
builder.base_url(None);
```
Still disallowed:
```rust
client.set_flag(true); // Method name does not match parameter `enabled`.
options.enabled(false, /*retry_count*/ 3); // More than one non-self argument.
options.enabled(/*value*/ false); // Explicit comment does not match `enabled`.
```
## Validation
Added UI coverage for boolean, numeric, and `None` builder arguments,
multi-argument methods, and explicit comment mismatches. Ran `rustup run
nightly-2025-09-18 cargo test` in `tools/argument-comment-lint`.
## Why
Bazel compiles Cargo build scripts in the exec configuration. For
`openssl-sys`, that means the target-specific optional `openssl-src`
dependency can disappear when producing musl release binaries, even
though the build script still needs the vendored source crate.
## What changed
Patch `rules_rs` to expose its existing unconditional
`build_script_deps` input through `crate.annotation`, then annotate
`openssl-sys` with the pinned `openssl-src` target. Target-derived build
dependencies continue to use the existing selected dependency path.
## Validation
- `just bazel-lock-check`
Stack: 2 of 6. Follows #27321.
## Intent
Keep release-specific Bazel helpers out of the shared Rust crate
definitions and colocate them with Bazel platform configuration.
## Implementation
Moves `multiplatform_binaries` and its platform list from `defs.bzl`
into `bazel/platforms/release_binaries.bzl` and updates the CLI load
site. Behavior is unchanged.
## Validation
- `bazel query //codex-rs/cli:release_binaries`
Stack: 1 of 6.
## Why
We're now [discouraging use of
`async_trait`](https://github.com/openai/codex/pull/20242).
Removing use of `async_trait` from `ToolExecutor` yields a `codex_core`
debug test build speedup of ~78% (from 227.5s to 50.3s) on my machine.
Stacked on #27299, this PR applies the trait change after the handler
bodies have been outlined.
## What
Changed `ToolExecutor::handle` to return an explicit boxed
`ToolExecutorFuture` instead of using `async_trait`.
Updated ToolExecutor implementors to return `Box::pin(...)`, reexported
the future alias through `codex-tools` and `codex-extension-api`, and
removed `codex-tools` direct `async-trait` dependency.
## Why
We're now [discouraging use of
`async_trait`](https://github.com/openai/codex/pull/20242).
Removing use of `async_trait` from `ToolExecutor` yields a `codex_core`
debug test build speedup of ~78% (from 227.5s to 50.3s) on my machine.
For ease of reviewing, this is a prefactor to extract trait method
implementations to inherent methods. This will prevent changing
indentation from creating a huge diff.
## What
Outlined existing `ToolExecutor::handle` bodies into inherent async
`handle_call` methods across core and extension tool handlers.
The trait methods still use `async_trait` and now delegate to
`self.handle_call(...).await`; handler behavior is unchanged.
## Why
Discovered some rough edges in the API while making use of it more
widely within exec-server. It would be a lot more convenient for
existing users of `AbsolutePathBuf` if `PathUri` conversion methods
returned `std::io::Result`s.
## What
* `PathUri::to_native_path()` -> `PathUri::to_abs_path()`
* `PathUri::from_file_path()` -> `PathUri::from_abs_path()`
## Why
Recent merges left `main` with analytics integration build failures.
Local Cargo runs also made the trimmed-skills test depend on
developer-installed skills, while Bazel used an isolated home.
## What changed
- Clone `thread_metadata.thread_source` when constructing goal analytics
event parameters.
- Group app-server thread extension inputs into
`ThreadExtensionDependencies`.
- Isolate the trimmed-skills test home so its exact fixture count is
stable across Cargo and Bazel.
## Validation
- `cargo check -p codex-analytics`
- `just test -p codex-analytics` (71 tests)
- `just test -p codex-app-server` (837 tests; one unrelated zsh-fork
timeout passed on retry)
## Why
Persisted MCP OAuth credentials were reported as authenticated whenever
a credential record existed. An expired token without a usable refresh
token could therefore appear as `OAuth` even though startup could not
authenticate with it, leaving users with a misleading status instead of
a login prompt.
## What changed
- Classify stored OAuth credentials as missing, usable, or requiring
authorization.
- Reuse the existing refresh window so near-expiry credentials without a
refresh path are also treated as logged out.
- Validate required credential fields before reporting OAuth
authentication.
- Add unit coverage for credential usability and integration coverage
for expired, unexpired, and refreshable persisted credentials.
## Validation
- `just test -p codex-rmcp-client`
## Why
Global instruction behavior spans thread creation, resume, forks,
subagents, and compaction. Characterization coverage is needed before
changing those semantics so preserved history can be distinguished from
newly loaded configuration.
## What changed
- Extends the existing `agents_md` suite with fresh-thread, warning,
resume, fork, and subagent lifecycle coverage.
- Extends the existing `compact` suite with manual, mid-turn, and
remote-v2 compaction coverage.
- Asserts rendered instruction fragments, reported source paths, and
structured request history before and after instruction-file mutations.
## Why
`just test` currently uses the CI-oriented nextest profile, which
serializes app-server integration tests even on developer machines that
can run several safely. Bounded local parallelism substantially shortens
this common iteration loop without changing CI behavior.
Eight-worker experiments were faster, but keeping them reliable required
relaxing several test deadlines. Four workers for integration tests is a
solid tradeoff that speeds up local testing without needing to change
test logic.
## What changed
- Add a `local` nextest profile that inherits the existing defaults.
- Allow up to four app-server integration tests to run concurrently
under that profile.
- Make `just test` select the local profile on Unix and Windows.
- Keep the default CI profile serialized and leave all test deadlines
unchanged.
The tests use separate processes, randomized temporary `CODEX_HOME`
directories, and ephemeral ports. The remaining shared constraints are
system resources; each app-server also uses a multi-thread Tokio
runtime, and fuzzy-search tests can create additional worker threads, so
the local cap remains intentionally conservative.
## Performance and validation
All measurements below are warm, execution-only app-server runs with
nextest retries disabled.
On the current rebased branch, an AMD EPYC 7763 machine with 16 logical
CPUs and 62 GiB RAM completed three consecutive runs:
| Run | Nextest time | Wall time | Result |
| --- | ---: | ---: | --- |
| 1 | 142.941s | 145.17s | 836/836 passed |
| 2 | 143.402s | 145.59s | 836/836 passed |
| 3 | 142.870s | 145.08s | 836/836 passed |
The mean wall time was 145.28s. The slow-inventory, approval replay, and
zsh-fork tests all passed with their original deadlines.
Earlier measurements on the same Linux machine, before the suite grew,
showed the scaling that motivated the change:
| App-server concurrency | Nextest time | Result |
| --- | ---: | --- |
| 1 | 369.5s | 572/572 passed |
| 2 | 194.5s | 572/572 passed |
| 4 | 111.0s mean over 3 runs | 3/3 clean |
Four workers reduced that execution time by about 70%, a roughly 3.3x
speedup over serialization.
## Why
`codex-extension-api` defines contracts shared by extension crates and
their hosts, but it had no direct test suite. Host and feature tests
cover downstream behavior, while regressions in the API crate's own
typed state, registry ordering, and capability adapters could go
unnoticed.
## What
- Add public-surface integration tests for `ExtensionData`, including
concurrent initialization and poison recovery.
- Cover contributor registration order, approval short-circuiting, event
sink retention, no-op response injection, and closure-based agent
spawning.
- Add the test-only dependencies used by the suite.
## Validation
- `just test -p codex-extension-api`
- `just argument-comment-lint -p codex-extension-api`
- `just bazel-lock-check`
## Why
The PR babysitter could surface inline comments from a GitHub review
that was still in the `PENDING` state. That allowed Codex to start
acting on feedback before the reviewer submitted it.
## What changed
- Correlate inline comments with their parent review and ignore pending
reviews and their comments.
- Remove pending review IDs from saved watcher state so the feedback
surfaces normally after publication.
- Update the skill instructions and add regression coverage for the
draft-to-published transition.
## Validation
- `python3 -m pytest
.codex/skills/babysit-pr/scripts/test_gh_pr_watch.py`
- Skill package validation with `quick_validate.py`
- Live verification on #26835: the draft comment stayed hidden and
surfaced after the review was submitted.
## Why
Codex needs stable `file:` URI identifiers that can cross process and
operating-system boundaries without eagerly interpreting them as native
paths. Existing fields also need to keep accepting absolute path strings
during migration.
## What changed
- Add `codex-utils-path-uri` with a validated, immutable `PathUri`
wrapper that currently accepts only `file:` URLs.
- Expose URI-level `basename`, `parent`, and `join` operations that
preserve authorities and percent encoding without guessing the source
operating system.
- Keep native conversion explicit through `AbsolutePathBuf` and the
current host rules.
- Serialize as canonical URI text while accepting both URI text and
legacy absolute native paths during deserialization.
- Add adversarial coverage for Windows-looking and POSIX paths, UNC
authorities, encoded metadata characters, non-UTF-8 POSIX paths, URI
hierarchy operations, and legacy serde round trips.
# Why
Codex is doing a bit too much on my PRs that it's babysitting. In
particular I'd like it to not interact with comment threads that involve
other humans -- I should be the one doing human interaction. This is
tricky because it's still very useful to be able to drop review comments
myself and have Codex iterate on them.
## What
This updates `.codex/skills/babysit-pr/SKILL.md` with an explicit GitHub
state mutation policy.
## Why
`just test` should run the test suite without also compiling and
executing benchmark smoke tests. Keeping benchmark validation explicit
avoids adding unrelated work to every project-specific test invocation.
## What changed
- Remove the `just bench-smoke` step from the Unix and Windows `test`
recipes.
- Document `just bench` and `just bench-smoke` as the explicit benchmark
commands in `AGENTS.md`.
## Validation
- `just test -p codex-arg0`
- `just --dry-run test`
- `just --dry-run bench-smoke`
## Why
`just bazel-clippy` ran target discovery with
`--noexperimental_remote_repo_contents_cache`, then ran the build with
the workspace default `--experimental_remote_repo_contents_cache`. Bazel
therefore killed and restarted its server on each transition, slowing
repeated commands and discarding the in-memory analysis cache. An audit
found the same class of startup-option variation in several CI command
sequences.
## What changed
- Keep local lint target-discovery queries on the workspace-default
Bazel server, while making CI target discovery explicitly use the CI
startup options.
- Normalize GitHub Actions launches through the BuildBuddy wrapper to
share `BAZEL_OUTPUT_USER_ROOT` and
`--noexperimental_remote_repo_contents_cache`.
- Route the CI lockfile check and Windows test-shard query through the
same startup configuration.
- Document the startup-option invariant and add wrapper regression
coverage.
## Validation
- Confirmed consecutive local clippy target-discovery runs retained the
same Bazel server PID.
## Why
Codex persists OAuth expiry as an absolute `expires_at`, then
reconstructs RMCP’s relative `expires_in` when credentials are loaded.
For an already-expired token, Codex reconstructed `expires_in` as
missing.
[RMCP 0.15 treated a missing `expires_in` as zero when a refresh token
was
present](https://github.com/modelcontextprotocol/rust-sdk/blob/9cfc905a9ef17c8bba6748dc0a9bdd2452681733/crates/rmcp/src/transport/auth.rs#L704-L723),
so this still triggered a refresh. [RMCP 1.7 treats missing expiry
information as unknown and uses the access token
as-is](https://github.com/modelcontextprotocol/rust-sdk/blob/3529c3675ff64db805bd947ca6ece6090809e43d/crates/rmcp/src/transport/auth.rs#L1233-L1265),
causing the stale token to be sent during `initialize`.
## What changed
- Represent a known-expired persisted token as `expires_in = 0`,
preserving `None` for genuinely unknown expiry.
- Add Streamable HTTP coverage requiring the token to refresh before the
startup handshake.
## Validation
- The new regression test fails on RMCP 1.7 before the fix and passes
afterward.
- The same scenario passes on the commit immediately before the RMCP 1.7
update, using RMCP 0.15.
- `just test -p codex-rmcp-client` (63 passed).
## Why
Codex agents that modify GitHub Actions configuration need clear
guidance when repository push protections require temporary approval.
Without it, an agent may pursue an unavailable exemption or stop before
checking whether the user already has access.
## What
Add a `pushing-ci-changes` skill that explains the restriction, directs
agents to attempt the push first, and tells them how to involve the user
when approval is required.
## Validation
Not run; this change only adds skill documentation.
## Intent
Follow up on #26205 by avoiding unnecessary filesystem canonicalization
during `AGENTS.md` discovery. The configured working directory is
already absolute, and canonicalization incorrectly switches symlinked
workspaces from their logical parent hierarchy to the target's
hierarchy.
## User-facing behavior
For a symlinked working directory such as:
```text
test-root/
|-- logical-repo/
| |-- AGENTS.md ("logical parent doc")
| `-- workspace ------------> physical-repo/workspace/
`-- physical-repo/
|-- AGENTS.md ("physical parent doc")
`-- workspace/
`-- AGENTS.md ("workspace doc")
```
Before this change, Codex canonicalized `logical-repo/workspace` to
`physical-repo/workspace` before discovery. It therefore loaded
`physical-repo/AGENTS.md` and `physical-repo/workspace/AGENTS.md`,
ignoring the instructions from the repository through which the user
entered the workspace.
After this change, ancestor discovery walks the configured logical path,
so Codex loads `logical-repo/AGENTS.md`. Opening
`logical-repo/workspace/AGENTS.md` still follows the symlink through the
host filesystem, so the workspace document is also loaded.
`physical-repo/AGENTS.md` is not loaded.
## Implementation
Use the logical absolute working directory when discovering project
instructions and reporting instruction sources. Filesystem reads still
follow the working-directory symlink, so an `AGENTS.md` in the target
workspace continues to load while ancestor discovery uses the symlink's
parents.
## Validation
Added integration coverage proving that discovery loads the logical
parent's instructions and the target workspace's instructions, but not
the target parent's instructions.
## Why
Fat LTO makes release builds substantially slower without providing
enough measured runtime benefit to justify the release CI long pole. The
build-profile investigation found that keeping Cargo's default release
`opt-level=3` and switching from fat LTO to ThinLTO (`3/thin/1`) reduced
a clean `codex-cli` release build from 2073.893 seconds to 1243.172
seconds, a 40.06% improvement.
The resulting binary increased from 196.7 MiB to 211.8 MiB (+7.63%).
Measured runtime changes were small: the worst image workload median was
+0.86% and app-server startup was +0.31% relative to fat LTO. ThinLTO
retains cross-crate optimization while avoiding most of the fat-LTO
build cost.
This deliberately avoids global size optimization: final-executable
testing showed a substantial regression on the image request path, which
is expected to become more important as image usage grows.
## What changed
- Set the workspace release profile to `lto = "thin"`, retaining Cargo's
default release `opt-level=3`.
- Remove release and CI workflow-specific LTO overrides so
release-profile builds consistently use the workspace setting.
- Remove the now-unused Windows release workflow input and related
diagnostic output.
## Validation
- Confirmed the release profile parses with `cargo metadata --no-deps
--format-version 1`.
- CI validates release builds across the supported target matrix.
## Why
Workspace-specific `AGENTS.md` loading needs to use the selected
environment filesystem so remote workspaces and child agents read
instructions from their actual environment instead of the host
filesystem. The app-server should report the same instruction sources
the initialized thread actually loaded, rather than independently
rescanning configuration and filesystem state.
## What changed
- Introduce `LoadedAgentsMd` to retain ordered user, project, and
internal instructions with their provenance.
- Load and canonicalize workspace `AGENTS.md` paths through the primary
`EnvironmentManager` environment, then render the loaded instructions
when constructing turn context.
- Expose cached loaded instruction sources from initialized threads and
use them for app-server start, resume, and fork responses.
- Preserve global `CODEX_HOME` loading and separator behavior while
excluding empty project files that did not supply model-visible
instructions.
- Add integration coverage for CLI injection, selected-environment
provenance and rendering, empty environment selection, and cached
sources on loaded-thread resume.
## Validation
- `just test -p codex-core agents_md`
- `just test -p codex-core
selected_environment_sources_match_model_visible_instructions`
- `just test -p codex-exec agents_md`
- `just test -p codex-app-server instruction_sources`
- `just test -p codex-app-server --status-level fail`
## Why
PR descriptions can be visible outside the context used to generate
them. In #23710, a generated description referenced an internal
document, showing that the skill needs an explicit guardrail against
exposing confidential context.
## What changed
- Updated the `codex-pr-body` guidance to prohibit confidential
references, including codenames and OpenAI-internal URLs.
## Why
Codex-created linked worktrees do not include ignored files from the
main worktree. Bazel users who keep local overrides in `user.bazelrc`
therefore lose those settings in every new worktree.
The setup must also work on Windows and must not overwrite a file that
already exists in the worktree.
## What changed
The checked-in Codex environment now invokes
`.codex/environments/setup.py`. The script resolves the main worktree
and current worktree, then uses
`copy_from_main_worktree_to_worktree(repo_relative_path)` to copy
ignored files into new worktrees without overwriting existing
destinations.
`main()` currently copies `user.bazelrc`. Additional repository-relative
paths can be added as further calls to the same helper.
## Validation
- Ran the setup script in a linked worktree and confirmed it handles a
missing main-worktree `user.bazelrc`.
- Verified the helper copies a main-worktree file, preserves an existing
worktree file, and creates parent directories for a nested path.
## Why
PR #25905 intentionally adds a failing `codex-core` unit test, but its
[Bazel test on Windows
check](https://github.com/openai/codex/actions/runs/26837526950/job/79135369259)
passed. That shows the Bazel configuration introduced by #25156 is not
behaving as expected, so revert it while the configuration can be
investigated separately.
## What changed
Revert #25156 in full, restoring the previous Bazel remote
configuration, CI scripts, workflows, `rusty_v8` handling, and
documentation. This removes the shared BuildBuddy wrapper and its tests.
## Validation
Not run locally; this exact revert was prioritized for a fast rollback.
## Why
Bazel remote configuration was selected in several CI scripts and
workflow steps. That made the BuildBuddy tenant policy easy to duplicate
and harder to audit, especially for fork pull requests that must not use
the OpenAI tenant.
This builds on
[sluongng/buildbuddy-ci-host-routing](https://github.com/openai/codex/compare/main...sluongng:codex:sluongng/buildbuddy-ci-host-routing)
and consolidates the policy in one place.
## What to do if this breaks you
See `codex-rs/docs/bazel.md` for details. TLDR:
1. make a BuildBuddy API key and put it in `~/.bazelrc`
2. if you're an OpenAI employee, add `common
--config=buildbuddy-openai-rbe` to `user.bazelrc` in the repo root
Run `just bazel-test` to ensure it works.
Note that `just bazel-remote-test` no longer exists, you need to select
a remote configuration as documented to use RBE.
## What changed
- Add `.github/scripts/run_bazel_with_buildbuddy.py` as the shared Bazel
wrapper and Python library. It selects the OpenAI host only for trusted
upstream GitHub Actions runs, routes keyed fork runs to the generic
host, and falls back to local Bazel execution when no key is available.
- Move endpoint selection into explicit `.bazelrc` configurations and
update Bazel CI, query helpers, and `rusty_v8` staging to use the shared
policy. Loading-phase target-discovery queries remain local.
- Add wrapper and `rusty_v8` unit coverage, plus `just test-scripts` for
the `.github/scripts` Python tests.
- Document local Bazel usage, `user.bazelrc` setup, BuildBuddy
configurations, and CI behavior in `codex-rs/docs/bazel.md`.
## Validation
- `just test-scripts`
- `bash -n .github/scripts/run-bazel-ci.sh
.github/scripts/run-bazel-query-ci.sh
.github/scripts/run-argument-comment-lint-bazel.sh
scripts/list-bazel-clippy-targets.sh`
- `python3 -m py_compile .github/scripts/run_bazel_with_buildbuddy.py
.github/scripts/test_run_bazel_with_buildbuddy.py
.github/scripts/test_rusty_v8_bazel.py
.github/scripts/rusty_v8_bazel.py`
- `ruff check .github/scripts/run_bazel_with_buildbuddy.py
.github/scripts/test_run_bazel_with_buildbuddy.py
.github/scripts/test_rusty_v8_bazel.py
.github/scripts/rusty_v8_bazel.py`
## Why
Cargo's libgit2 transport has intermittently failed while fetching git
dependencies with nested submodules.
[#25644](https://github.com/openai/codex/pull/25644) applied
`CARGO_NET_GIT_FETCH_WITH_CLI=true` to the main Rust release build after
macOS SecureTransport/libgit2 failures while cloning `libwebrtc`'s
nested `libyuv` submodule. Similar flakes can affect other Cargo-bearing
Rust jobs.
## What changed
Configure `CARGO_NET_GIT_FETCH_WITH_CLI=true` at workflow scope for the
remaining Cargo-bearing Rust workflows:
- fast Rust CI and `cargo-deny`
- reusable Windows and argument-comment-lint release workflows
- `rusty-v8-release` and `v8-canary` Cargo builds and smoke tests
The full Rust CI, reusable nextest workflow, and primary Rust release
build already had the override. Bazel-only workflows are unchanged
because they use a different dependency fetch path.
## Validation
- Parsed all `.github/workflows/*.yml` files as YAML.
- Scanned Cargo-bearing workflows to confirm they configure
`CARGO_NET_GIT_FETCH_WITH_CLI`.
## Why
The root formatting entrypoints could drift: `just fmt` did not format
the Justfile itself, and the CI-facing check recipe only checked Python
scripts instead of matching everything formatted by `just fmt`.
## What changed
- Add a shared cross-platform Python formatter driver used by both `just
fmt` and `just fmt-check`.
- Run Justfile, Rust, Python SDK, and internal-script formatter groups
concurrently while buffering each formatter group's output until it
finishes.
- Log formatter starts immediately, then print each formatter group's
labeled output when it completes.
- Keep the SDK lint-fix and Ruff formatting passes ordered, with source
comments explaining their distinct roles and the check-mode equivalents.
- Run Ruff through shared `uv run --no-sync --with ruff` overlays so
formatting works on clean glibc Linux checkouts without installing the
platform-specific SDK runtime wheel.
- Show `fmt-check` help text in `just -l` and simplify CI to call the
shared driver through `just fmt-check`.
- Pin the general CI workflow to `just@1.51.0` so its formatter agrees
with the checked-in Justfile.
- Add regression coverage for the thin Just recipes and the driver's
formatter graph.
## Validation
- `just fmt`
- `just fmt-check`
- `python3 -m pytest
sdk/python/tests/test_artifact_workflow_and_binaries.py -k 'root_fmt or
root_format' -q`
- `pnpm run format`
- `git diff --check`
- `just -l | rg -n '^ fmt|fmt-check'`
- `uvx --from uv==0.7.22 uv run --frozen --project sdk/python --no-sync
--with ruff ruff check --diff sdk/python`
## Why
Python contributions in this repository should target the declared
Python 3 runtime instead of carrying Python 2 compatibility patterns
forward. When compatibility across Python 3 point releases matters,
contributors need a consistent source of truth for the minimum supported
version.
## What changed
- Added Python development guidance to `AGENTS.md` stating that the
repository uses Python 3+ and should not use the `__future__` module.
- Documented that contributors should check the nearest `pyproject.toml`
`requires-python` field when evaluating Python 3 point-release
compatibility.
## Testing
Not run (guidance-only change).
## Why
New unit test modules should follow one consistent layout so
implementation files stay focused and test suites remain easy to locate,
without creating cleanup churn in existing inline test modules.
## What changed
- Added `AGENTS.md` guidance requiring new test modules to use separate
sibling `*_tests.rs` files with an explicit `#[path = "..._tests.rs"]`
attribute.
- Clarified that existing inline `#[cfg(test)] mod tests { ... }`
modules should not be moved solely to follow the new convention.
## Validation
- Ran `git diff --check`.
## Why
Python files under `scripts/` were not covered by the repository
formatting recipe or the CI formatting job, so formatting drift could
merge unnoticed.
## What
- Add a dedicated `scripts/pyproject.toml` and `scripts/uv.lock` so
root-script formatting uses a locked Ruff version.
- Extend `just fmt` to format root Python scripts and add
`fmt-scripts-check` for CI.
- Run `just fmt-scripts-check` from `.github/workflows/ci.yml`,
installing `uv` through SHA-pinned `astral-sh/setup-uv` while retaining
the `uv` `0.11.3` pin.
- Apply Ruff formatting to the root Python scripts, including
`scripts/just-shell.py`, and extend
`sdk/python/tests/test_artifact_workflow_and_binaries.py` to cover the
root formatting recipe.
- Update `AGENTS.md` so agents run `just fmt` after code changes
anywhere in the repository.
## Validation
- Extended the existing Python SDK workflow test to assert that `just
fmt` includes root Python scripts.
## Why
`codex_core` is consistently a bottleneck for incremental builds during
iteration. The simplest fix is to make the crate smaller.
## Summary
`codex-core` owns several reusable prompt renderers and static prompt
assets, which makes the crate harder to split apart.
Rename `codex-review-prompts` to `codex-prompts` and move shared review,
goal, permissions, compaction, realtime, hierarchical AGENTS.md, and
`apply_patch` prompts into it. Move prompt-only tests and update
consumers and `CODEOWNERS`.
## Validation
- `just test -p codex-prompts -p codex-apply-patch`
- `just test -p codex-core prompt_caching`
- Bazel builds for the affected crates
Ensures MCP-backed `codex-core` integration tests exercise initialized
servers instead of racing server startup.
I've been idly investigating a few flakes and the failure modes are much
more confusing when a tool call fails because of a failed server start
than when the failed server start causes the test to fail directly.
## Summary
- Add `--stdio` as a direct alias for `codex app-server --listen
stdio://`.
- Keep `--stdio` and `--listen` mutually exclusive.
- Update the app-server README to document both forms.
The codex-windows runner group should be much faster than the default
GHA runners. Since bazel jobs on windows are frequently the long pole
for PRs checks, this will hopefully get people landing a bit faster.
It seems that this was added to allow rustc to load proc macros that had
been compiled with UBSan enabled, which zig does for debug and
`ReleaseSafe` builds. When zig drives the link of the final binary it
knows to include the ubsan runtime, but our zig-built artifacts are
being linked into a binary whose linking rustc drives. This removes the
libubsan workaround we have and replaces it with
`-fno-sanitize=undefined` passed to zig.
The new argument is passed at the end of zig's args so should take
precedence over any earlier arguments from the script's caller.
## Summary
- Add a new `app-server-start-bench` crate to measure app-server startup
performance
- Wire the benchmark into the workspace and Bazel build so it can be run
consistently
- Update lockfiles and repo automation to account for the new package
WIll make it easier to uprev when the new draft spec is supported.
Also updates reqwest where needed for compatibility but doesn't update
it everywhere since this is already a large diff.
The new version of rmcp handles certain kinds of authentication failures
differently, this patch includes support for identifying the failing scope
in a WWW-Authenticate header.
## Summary
- Bump the workspace Rust toolchain from `1.93.0` to `1.95.0` across
Cargo, Bazel, CI, release workflows, devcontainers, and the Codex
environment config.
- Refresh `MODULE.bazel.lock` so the Bazel Rust toolchain artifacts
match the new version.
- Leave purpose-specific toolchains unchanged, including the
`argument-comment-lint` nightly and the upstream `rusty_v8` `1.91.0`
build pin.
- Includes fixes for new lints from `just fix` and a few codex-authored
fixes for lints without a suggestion.
## Summary
- add Divan benchmarks for prompt image re-encoding paths
- wire the image benchmark smoke test into Rust CI workflows
## Why
Image prompt handling includes re-encoding work that benefits from
repeatable benchmark coverage so changes can be measured in CI and
locally.
This already helped identify a potential regression from changing compiler flags.
## Impact
Developers can run and compare the new image re-encoding benchmarks, and
CI exercises the benchmark target via the Rust benchmark smoke test.
`cargo test` for the core and other crates fails on a fresh macOS
checkout without the right stack size variable. This change encourages
using the just test command that sets the environment up correctly.
As a bonus, this should encourage agents to get more benefit out of
nextest's parallel execution.
## Summary
- Treat MCP tools with `readOnlyHint: true` as parallel-safe even when
`supports_parallel_tool_calls` is unset or `false`.
- Keep server-level `supports_parallel_tool_calls` as an additive
override for non-read-only tools.
- Add focused unit coverage for the MCP handler eligibility decision.
- Update RMCP integration coverage to keep the serial baseline on a
mutable tool, verify read-only concurrency without server opt-in, and
preserve the server opt-in concurrency path separately.
## Testing
- `just fmt`
- `cargo test -p codex-core --lib tools::handlers::mcp::tests::`
- `cargo test -p codex-core --test all
stdio_mcp_read_only_tool_calls_run_concurrently_without_server_opt_in`
- `cargo test -p codex-core --test all
stdio_mcp_parallel_tool_calls_opt_in_runs_concurrently`
- `cargo test -p codex-rmcp-client`