Problem:
The `aarch64-unknown-linux-musl` release build was failing at link time with
`/usr/bin/ld: cannot find -lcap` while building binaries that transitively pull
in `codex-linux-sandbox`. CI did not include an arm64 linux musl representative
release build in `rust-ci`, so this release-only target regression was not
caught before release.
Why this is the right fix:
`codex-linux-sandbox` compiles vendored bubblewrap and links `libcap`. In the
musl jobs, we were installing distro `libcap-dev`, which provides host/glibc
artifacts. That is not a valid source of target-compatible static libcap for
musl cross-linking. The correct fix is to produce a target-compatible libcap
inside the musl tool bootstrap and point pkg-config at it.
Separately, adding arm64 linux musl to the representative release matrix in
`rust-ci` ensures this class of release-only breakage is caught by normal CI.
Using `thin` LTO for those representative release-profile checks keeps CI
feedback fast while still exercising release-profile behavior.
What changed:
- Updated `.github/scripts/install-musl-build-tools.sh` to install tooling
needed to fetch/build libcap sources (`curl`, `xz-utils`, certs).
- Added deterministic libcap bootstrap in the musl tool root:
- download `libcap-2.75` from kernel.org
- verify SHA256
- build with the target musl compiler (`*-linux-musl-gcc`)
- stage `libcap.a` and headers under the target tool root
- generate a target-scoped `libcap.pc`
- Exported target `PKG_CONFIG_PATH` so builds resolve the staged musl libcap
instead of host pkg-config/lib paths.
- Added `ubuntu-24.04-arm` + `aarch64-unknown-linux-musl` + `profile: release`
to the representative release builds in `.github/workflows/rust-ci.yml`.
- Configured `rust-ci` to use `CARGO_PROFILE_RELEASE_LTO=thin` for matrix
entries where `profile == release`.
Verification:
- Reproduced the original failure in CI-like containers:
- `aarch64-unknown-linux-musl` failed with `cannot find -lcap`.
- Verified the underlying mismatch by forcing host libcap into the link:
- link then failed with glibc-specific unresolved symbols
(`__isoc23_*`, `__*_chk`), confirming host libcap was unsuitable.
- Verified the fix in CI-like containers after this change:
- `cargo build -p codex-linux-sandbox --target aarch64-unknown-linux-musl --release` -> pass
- `cargo build -p codex-linux-sandbox --target x86_64-unknown-linux-musl --release` -> pass
Problem:
The `aarch64-unknown-linux-musl` release build was failing at link time with
`/usr/bin/ld: cannot find -lcap` while building binaries that transitively pull
in `codex-linux-sandbox`.
Why this is the right fix:
`codex-linux-sandbox` compiles vendored bubblewrap and links `libcap`. In the
musl jobs, we were installing distro `libcap-dev`, which provides host/glibc
artifacts. That is not a valid source of target-compatible static libcap for
musl cross-linking. The fix is to produce a target-compatible libcap inside the
musl tool bootstrap and point pkg-config at it.
What changed:
- Updated `.github/scripts/install-musl-build-tools.sh` to install tooling
needed to fetch/build libcap sources (`curl`, `xz-utils`, certs).
- Added deterministic libcap bootstrap in the musl tool root:
- download `libcap-2.75` from kernel.org
- verify SHA256
- build with the target musl compiler (`*-linux-musl-gcc`)
- stage `libcap.a` and headers under the target tool root
- generate a target-scoped `libcap.pc`
- Exported target `PKG_CONFIG_PATH` so builds resolve the staged musl libcap
instead of host pkg-config/lib paths.
Verification:
- Reproduced the original failure in CI-like containers:
- `aarch64-unknown-linux-musl` failed with `cannot find -lcap`.
- Verified the underlying mismatch by forcing host libcap into the link:
- link then failed with glibc-specific unresolved symbols
(`__isoc23_*`, `__*_chk`), confirming host libcap was unsuitable.
- Verified the fix in CI-like containers after this change:
- `cargo build -p codex-linux-sandbox --target aarch64-unknown-linux-musl --release` -> pass
- `cargo build -p codex-linux-sandbox --target x86_64-unknown-linux-musl --release` -> pass
## Why
We want actionable build-hotspot data from CI so we can tune Rust
workflow performance (for example, target coverage, cache behavior, and
job shape) based on actual compile-time bottlenecks.
`cargo` timing reports are lightweight and provide a direct way to
inspect where compilation time is spent.
## What Changed
- Updated `.github/workflows/rust-release.yml` to run `cargo build` with
`--timings` and upload `target/**/cargo-timings/cargo-timing.html`.
- Updated `.github/workflows/rust-release-windows.yml` to run `cargo
build` with `--timings` and upload
`target/**/cargo-timings/cargo-timing.html`.
- Updated `.github/workflows/rust-ci.yml` to:
- run `cargo clippy` with `--timings`
- run `cargo nextest run` with `--timings` (stable-compatible)
- upload `target/**/cargo-timings/cargo-timing.html` artifacts for both
the clippy and nextest jobs
Artifacts are matrix-scoped via artifact names so timings can be
compared per target/profile.
## Verification
- Confirmed the net diff is limited to:
- `.github/workflows/rust-ci.yml`
- `.github/workflows/rust-release.yml`
- `.github/workflows/rust-release-windows.yml`
- Verified timing uploads are added immediately after the corresponding
timed commands in each workflow.
- Confirmed stable Cargo accepts plain `--timings` for the compile phase
(`cargo test --no-run --timings`) and generates
`target/cargo-timings/cargo-timing.html`.
- Ran VS Code diagnostics on modified workflow files; no new diagnostics
were introduced by these changes.
## Why
`project_doc::tests::skills_are_appended_to_project_doc` and
`project_doc::tests::skills_render_without_project_doc` were assuming a
single synthetic skill in test setup, but they called
`load_skills(&cfg)`, which loads from repo/user/system roots.
That made the assertions environment-dependent. After
[#11531](https://github.com/openai/codex/pull/11531) added
`.codex/skills/test-tui/SKILL.md`, the repo-scoped `test-tui` skill
began appearing in these test outputs and exposed the flake.
## What Changed
- Added a test-only helper in `codex-rs/core/src/project_doc.rs` that
loads skills from an explicit root via `load_skills_from_roots`.
- Scoped that root to `codex_home/skills` with `SkillScope::User`.
- Updated both affected tests to use this helper instead of
`load_skills(&cfg)`:
- `skills_are_appended_to_project_doc`
- `skills_render_without_project_doc`
This keeps the tests focused on the fixture skills they create,
independent of ambient repo/home skills.
## Verification
- `cargo test -p codex-core
project_doc::tests::skills_render_without_project_doc -- --exact`
- `cargo test -p codex-core
project_doc::tests::skills_are_appended_to_project_doc -- --exact`
## Summary
This PR removes the temporary `CODEX_BWRAP_ENABLE_FFI` flag and makes
Linux builds always compile vendored bubblewrap support for
`codex-linux-sandbox`.
## Changes
- Removed `CODEX_BWRAP_ENABLE_FFI` gating from
`codex-rs/linux-sandbox/build.rs`.
- Linux builds now fail fast if vendored bubblewrap compilation fails
(instead of warning and continuing).
- Updated fallback/help text in
`codex-rs/linux-sandbox/src/vendored_bwrap.rs` to remove references to
`CODEX_BWRAP_ENABLE_FFI`.
- Removed `CODEX_BWRAP_ENABLE_FFI` env wiring from:
- `.github/workflows/rust-ci.yml`
- `.github/workflows/bazel.yml`
- `.github/workflows/rust-release.yml`
---------
Co-authored-by: David Zbarsky <zbarsky@openai.com>
## Why
Installing `zstd` via Chocolatey in
`.github/workflows/rust-release-windows.yml` has been taking about a
minute on Windows release runs. This adds avoidable latency to each
release job.
Using DotSlash removes that package-manager install step and pins the
exact binary we use for compression.
## What Changed
- Added `.github/workflows/zstd`, a DotSlash wrapper that fetches
`zstd-v1.5.7-win64.zip` with pinned size and digest.
- Updated `.github/workflows/rust-release-windows.yml` to:
- install DotSlash via `facebook/install-dotslash@v2`
- replace `zstd -T0 -19 ...` with
`${GITHUB_WORKSPACE}/.github/workflows/zstd -T0 -19 ...`
- `windows-aarch64` uses the same win64 upstream zstd artifact because
upstream releases currently publish `win32` and `win64` binaries.
## Verification
- Verified the workflow now resolves the DotSlash file from
`${GITHUB_WORKSPACE}` while the job runs with `working-directory:
codex-rs`.
- Ran VS Code diagnostics on changed files:
- `.github/workflows/rust-release-windows.yml`
- `.github/workflows/zstd`
## Why
`rust-release` cache restore has had very low practical value, while
cache save consistently costs significant time (usually adding ~3
minutes to the critical path of a release workflow).
From successful release-tag runs with cache steps (`289` runs total):
- Alpha tags: cache download averaged ~5s/run, cache upload averaged
~230s/run.
- Stable tags: cache download averaged ~5s/run, cache upload averaged
~227s/run.
- Windows release builds specifically: download ~2s/run vs upload
~169-170s/run.
Hard step-level signal from the same successful release-tag runs:
- Cache restore (`Run actions/cache`): `2,314` steps, total `1,515s`
(~0.65s/step).
- `95.3%` of restore steps finished in `<=1s`; `99.7%` finished in
`<=2s`; `0` steps took `>=10s`.
- Cache save (`Post Run actions/cache`): `2,314` steps, total `66,295s`
(~28.65s/step).
Run-level framing:
- Download total was `<=10s` in `288/289` runs (`99.7%`).
- Upload total was `>=120s` in `285/289` runs (`98.6%`).
The net effect is that release jobs are spending time uploading caches
that are rarely useful for subsequent runs.
## What Changed
- Removed the `actions/cache@v5` step from
`.github/workflows/rust-release.yml`.
- Removed the `actions/cache@v5` step from
`.github/workflows/rust-release-windows.yml`.
- Left build, signing, packaging, and publishing flow unchanged.
## Validation
- Queried historical `rust-release` run/job step timing and compared
cache download vs upload for alpha and stable release tags.
- Spot-checked release logs and observed repeated `Cache not found ...`
followed by `Cache saved ...` patterns.
`SandboxPolicy::ReadOnly` previously implied broad read access and could
not express a narrower read surface.
This change introduces an explicit read-access model so we can support
user-configurable read restrictions in follow-up work, while preserving
current behavior today.
It also ensures unsupported backends fail closed for restricted-read
policies instead of silently granting broader access than intended.
## What
- Added `ReadOnlyAccess` in protocol with:
- `Restricted { include_platform_defaults, readable_roots }`
- `FullAccess`
- Updated `SandboxPolicy` to carry read-access configuration:
- `ReadOnly { access: ReadOnlyAccess }`
- `WorkspaceWrite { ..., read_only_access: ReadOnlyAccess }`
- Preserved existing behavior by defaulting current construction paths
to `ReadOnlyAccess::FullAccess`.
- Threaded the new fields through sandbox policy consumers and call
sites across `core`, `tui`, `linux-sandbox`, `windows-sandbox`, and
related tests.
- Updated Seatbelt policy generation to honor restricted read roots by
emitting scoped read rules when full read access is not granted.
- Added fail-closed behavior on Linux and Windows backends when
restricted read access is requested but not yet implemented there
(`UnsupportedOperation`).
- Regenerated app-server protocol schema and TypeScript artifacts,
including `ReadOnlyAccess`.
## Compatibility / rollout
- Runtime behavior remains unchanged by default (`FullAccess`).
- API/schema changes are in place so future config wiring can enable
restricted read access without another policy-shape migration.
## Why
`suite::v2::app_list::list_apps_uses_thread_feature_flag_when_thread_id_is_provided`
has been flaky in CI. The test exercises `thread/start`, which
initializes `codex_apps`. In CI/Linux, that path can reach OS
keyring-backed MCP OAuth credential lookup (`Codex MCP Credentials`) and
intermittently abort the MCP process (observed stack overflow in
`zbus`), causing the test to fail before the assertion logic runs.
## What Changed
- Updated the test config in
`codex-rs/app-server/tests/suite/v2/app_list.rs` to set
`mcp_oauth_credentials_store = "file"` in both relevant config-writing
paths:
- The in-test config override inside
`list_apps_uses_thread_feature_flag_when_thread_id_is_provided`
- `write_connectors_config(...)`, which is used by the v2 `app_list`
test suite
- This keeps test coverage focused on thread-scoped app feature flags
while removing OS keyring/DBus dependency from this test path.
## How It Was Verified
- `cargo test -p codex-app-server`
- `cargo test -p codex-app-server
list_apps_uses_thread_feature_flag_when_thread_id_is_provided --
--nocapture`
This is in response to seeing this on BuildBuddy:
> There were tests whose specified size is too big. Use the
--test_verbose_timeout_warnings command line option to see which ones
these are.
- Update token usage aggregation to refresh model context window after a
model change.
- Add protocol/core tests, including an e2e model-switch test that
validates switching to a smaller model updates telemetry.
- Clamp auto-compaction to the minimum of configured limit and 90% of
context window
- Add an e2e compact test for clamped behavior
- Update remote compact tests to account for earlier auto-compaction in
setup turns
- Run pre-sampling compact through a single helper that builds
previous-model turn context and compacts before the follow-up request
when switching to a smaller context window.
- Keep compaction events on the parent turn id and add compact suite
coverage for switch-in-session and resume+switch flows.
# 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.
## What
This change introduces a new `ReadOnlyAccess` model and threads it through
sandbox policy consumers so read access is explicit instead of implicit.
- Added `ReadOnlyAccess` to protocol:
- `Restricted { include_platform_defaults, readable_roots }`
- `FullAccess`
- Changed `SandboxPolicy` shape:
- `ReadOnly` is now `ReadOnly { access: ReadOnlyAccess }`
- `WorkspaceWrite` now carries `read_only_access: ReadOnlyAccess`
- Kept existing behavior for now by defaulting to `ReadOnlyAccess::FullAccess`
in constructors and current config/app-server mappings.
- Added helper methods to compute effective readable roots (including optional
platform defaults + cwd) and to detect full read access.
- Updated seatbelt policy generation to honor restricted read roots by emitting
scoped `(allow file-read* ...)` entries when full read access is not granted.
- Updated Linux backends (`bwrap`, legacy landlock path) to fail closed with an
explicit `UnsupportedOperation` when restricted read access is requested but
not yet implemented there.
- Updated Windows sandbox backends (standard, elevated, and runner paths) to
fail closed in the same way for restricted read access.
- Updated all call sites/tests/pattern matches for the new structured variants
and regenerated app-server protocol schema/types.
## Why
The previous `SandboxPolicy::ReadOnly` implied full-disk read access and left
no way to express a narrower read surface.
This refactor establishes the policy model needed to support user-configurable
read restrictions in a follow-up without changing current runtime behavior.
It also ensures we do not silently ignore future restricted-read policies on
platform backends that do not support them yet. Failing closed keeps sandbox
semantics predictable and avoids accidental over-permission.
## Compatibility and rollout notes
- Existing behavior is preserved by default (`FullAccess`).
- Existing config/app-server flows continue to serialize/deserialize cleanly.
- New schema artifacts are included to keep generated protocol outputs in sync.
## Validation
- `just fmt`
- `just fix -p codex-protocol -p codex-core -p codex-linux-sandbox -p codex-windows-sandbox -p codex-app-server-protocol`
- `cargo check -p codex-windows-sandbox`
- Targeted crate/test runs were executed during development for protocol/core/
sandbox-related crates.
## What
This change introduces a new `ReadOnlyAccess` model and threads it through
sandbox policy consumers so read access is explicit instead of implicit.
- Added `ReadOnlyAccess` to protocol:
- `Restricted { include_platform_defaults, readable_roots }`
- `FullAccess`
- Changed `SandboxPolicy` shape:
- `ReadOnly` is now `ReadOnly { access: ReadOnlyAccess }`
- `WorkspaceWrite` now carries `read_only_access: ReadOnlyAccess`
- Kept existing behavior for now by defaulting to `ReadOnlyAccess::FullAccess`
in constructors and current config/app-server mappings.
- Added helper methods to compute effective readable roots (including optional
platform defaults + cwd) and to detect full read access.
- Updated seatbelt policy generation to honor restricted read roots by emitting
scoped `(allow file-read* ...)` entries when full read access is not granted.
- Updated Linux backends (`bwrap`, legacy landlock path) to fail closed with an
explicit `UnsupportedOperation` when restricted read access is requested but
not yet implemented there.
- Updated Windows sandbox backends (standard, elevated, and runner paths) to
fail closed in the same way for restricted read access.
- Updated all call sites/tests/pattern matches for the new structured variants
and regenerated app-server protocol schema/types.
## Why
The previous `SandboxPolicy::ReadOnly` implied full-disk read access and left
no way to express a narrower read surface.
This refactor establishes the policy model needed to support user-configurable
read restrictions in a follow-up without changing current runtime behavior.
It also ensures we do not silently ignore future restricted-read policies on
platform backends that do not support them yet. Failing closed keeps sandbox
semantics predictable and avoids accidental over-permission.
## Compatibility and rollout notes
- Existing behavior is preserved by default (`FullAccess`).
- Existing config/app-server flows continue to serialize/deserialize cleanly.
- New schema artifacts are included to keep generated protocol outputs in sync.
## Validation
- `just fmt`
- `just fix -p codex-protocol -p codex-core -p codex-linux-sandbox -p codex-windows-sandbox -p codex-app-server-protocol`
- `cargo check -p codex-windows-sandbox`
- Targeted crate/test runs were executed during development for protocol/core/
sandbox-related crates.
## Summary
- Remove `Feature::SearchTool` and the `search_tool` config key from the
feature registry/schema.
- Gate `search_tool_bm25` exposure via `Feature::Apps` in
`core/src/tools/spec.rs`.
- Update MCP selection logic in `core/src/codex.rs` to use
`Feature::Apps` for search-tool behavior.
- Update `core/tests/suite/search_tool.rs` to enable `Feature::Apps`.
- Regenerate `core/config.schema.json` via `just write-config-schema`.
## Testing
- `just fmt`
- `cargo test -p codex-core --test all suite::search_tool::`
## Tickets
- None
I gave Codex the following bug report about the logic to report the
host's resources introduced in
https://github.com/openai/codex/pull/11488 and this PR is its proposed
fix.
The fix seems like an escaping issue, mostly.
---
The logic to print out the runner specs has an awk error on Mac:
```
Runner: GitHub Actions 1014936475
OS: macOS 15.7.3
Hardware model: VirtualMac2,1
CPU architecture: arm64
Logical CPUs: 5
Physical CPUs: 5
awk: syntax error at source line 1
context is
{printf >>> \ <<< "%.1f GiB\\n\", $1 / 1024 / 1024 / 1024}
awk: illegal statement at source line 1
Total RAM:
Disk usage:
Filesystem Size Used Avail Capacity iused ifree %iused Mounted on
/dev/disk3s5 320Gi 237Gi 64Gi 79% 2.0M 671M 0% /System/Volumes/Data
```
as well as Linux:
```
Runner: GitHub Actions 1014936469
OS: Linux runnervmwffz4 6.11.0-1018-azure #18~24.04.1-Ubuntu SMP Sat Jun 28 04:46:03 UTC 2025 x86_64 x86_64 x86_64 GNU/Linux
awk: cmd. line:1: /Model name/ {gsub(/^[ \t]+/,\"\",$2); print $2; exit}
awk: cmd. line:1: ^ backslash not last character on line
CPU model:
Logical CPUs: 4
awk: cmd. line:1: /MemTotal/ {printf \"%.1f GiB\\n\", $2 / 1024 / 1024}
awk: cmd. line:1: ^ backslash not last character on line
Total RAM:
Disk usage:
Filesystem Size Used Avail Use% Mounted on
/dev/root 72G 50G 22G 70% /
```