## Why
Bazel clippy now catches lints that `cargo clippy` can still miss when a
crate under `codex-rs` forgets to opt into workspace lints. The concrete
example here was `codex-rs/app-server/tests/common/Cargo.toml`: Bazel
flagged a clippy violation in `models_cache.rs`, but Cargo did not
because that crate inherited workspace package metadata without
declaring `[lints] workspace = true`.
We already mirror the workspace clippy deny list into Bazel after
[#15955](https://github.com/openai/codex/pull/15955), so we also need a
repo-side check that keeps every `codex-rs` manifest opted into the same
workspace settings.
## What changed
- add `.github/scripts/verify_cargo_workspace_manifests.py`, which
parses every `codex-rs/**/Cargo.toml` with `tomllib` and verifies:
- `version.workspace = true`
- `edition.workspace = true`
- `license.workspace = true`
- `[lints] workspace = true`
- top-level crate names follow the `codex-*` / `codex-utils-*`
conventions, with explicit exceptions for `windows-sandbox-rs` and
`utils/path-utils`
- run that script in `.github/workflows/ci.yml`
- update the current outlier manifests so the check is enforceable
immediately
- fix the newly exposed clippy violations in the affected crates
(`app-server/tests/common`, `file-search`, `feedback`,
`shell-escalation`, and `debug-client`)
---
[//]: # (BEGIN SAPLING FOOTER)
Stack created with [Sapling](https://sapling-scm.com). Best reviewed
with [ReviewStack](https://reviewstack.dev/openai/codex/pull/16353).
* #16351
* __->__ #16353
## Why
https://github.com/openai/codex/pull/16287 introduced a change to
`codex-rs/login/src/auth/auth_tests.rs` that uses a PowerShell helper to
read the next token from `tokens.txt` and rewrite the remainder back to
disk. On Windows, `Get-Content` can return a scalar when the file has
only one remaining line, so `$lines[0]` reads the first character
instead of the full token. That breaks the external bearer refresh test
once the token list is nearly exhausted.
https://github.com/openai/codex/pull/16288 introduced similar changes to
`codex-rs/core/src/models_manager/manager_tests.rs` and
`codex-rs/core/tests/suite/client.rs`.
These went unnoticed because the failures showed up when the test was
run via Cargo on Windows, but not in our Bazel harness. Figuring out
that Cargo-vs-Bazel delta will happen in a follow-up PR.
## Verification
On my Windows machine, I verified `cargo test` passes when run in
`codex-rs/login` and `codex-rs/core`. Once this PR is merged, I will
keep an eye on
https://github.com/openai/codex/actions/workflows/rust-ci-full.yml to
verify it goes green.
## What changed
- Wrap `Get-Content -Path tokens.txt` in `@(...)` so the script always
gets array semantics before counting, indexing, and rewriting the
remaining lines.
I noticed that
https://github.com/openai/codex/actions/workflows/rust-ci-full.yml
started failing on my own PR,
https://github.com/openai/codex/pull/16288, even though CI was green
when I merged it.
Apparently, it introduced a lint violation that was [correctly!] caught
by our Cargo-based clippy runner, but not our Bazel-based one.
My next step is to figure out the reason for the delta between the two
setups, but I wanted to get us green again quickly, first.
Adds this:
```
properties.insert(
"fork_turns".to_string(),
JsonSchema::String {
description: Some(
"Optional MultiAgentV2 fork mode. Use `none`, `all`, or a positive integer string such as `3` to fork only the most recent turns."
.to_string(),
),
},
);
```
---------
Co-authored-by: Codex <noreply@openai.com>
## Summary
Fixes#15189.
Custom model providers that set `requires_openai_auth = false` could
only use static credentials via `env_key` or
`experimental_bearer_token`. That is not enough for providers that mint
short-lived bearer tokens, because Codex had no way to run a command to
obtain a bearer token, cache it briefly in memory, and retry with a
refreshed token after a `401`.
This PR adds that provider config and wires it through the existing auth
design: request paths still go through `AuthManager.auth()` and
`UnauthorizedRecovery`, with `core` only choosing when to use a
provider-backed bearer-only `AuthManager`.
## Scope
To keep this PR reviewable, `/models` only uses provider auth for the
initial request in this change. It does **not** add a dedicated `401`
retry path for `/models`; that can be follow-up work if we still need it
after landing the main provider-token support.
## Example Usage
```toml
model_provider = "corp-openai"
[model_providers.corp-openai]
name = "Corp OpenAI"
base_url = "https://gateway.example.com/openai"
requires_openai_auth = false
[model_providers.corp-openai.auth]
command = "gcloud"
args = ["auth", "print-access-token"]
timeout_ms = 5000
refresh_interval_ms = 300000
```
The command contract is intentionally small:
- write the bearer token to `stdout`
- exit `0`
- any leading or trailing whitespace is trimmed before the token is used
## What Changed
- add `model_providers.<id>.auth` to the config model and generated
schema
- validate that command-backed provider auth is mutually exclusive with
`env_key`, `experimental_bearer_token`, and `requires_openai_auth`
- build a bearer-only `AuthManager` for `ModelClient` and
`ModelsManager` when a provider configures `auth`
- let normal Responses requests and realtime websocket connects use the
provider-backed bearer source through the same `AuthManager.auth()` path
- allow `/models` online refresh for command-auth providers and attach
the provider token to the initial `/models` request
- keep `auth.cwd` available as an advanced escape hatch and include it
in the generated config schema
## Testing
- `cargo test -p codex-core provider_auth_command`
- `cargo test -p codex-core
refresh_available_models_uses_provider_auth_token`
- `cargo test -p codex-core
test_deserialize_provider_auth_config_defaults`
## Docs
- `developers.openai.com/codex` should document the new
`[model_providers.<id>.auth]` block and the token-command contract
## Why
The Bazel-backed `argument-comment-lint` CI path had two gaps:
- Bazel wildcard target expansion skipped inline unit-test crates from
`src/` modules because the generated `*-unit-tests-bin` `rust_test`
targets are tagged `manual`.
- `argument-comment-mismatch` was still only a warning in the Bazel and
packaged-wrapper entrypoints, so a typoed `/*param_name*/` comment could
still pass CI even when the lint detected it.
That left CI blind to real linux-sandbox examples, including the missing
`/*local_port*/` comment in
`codex-rs/linux-sandbox/src/proxy_routing.rs` and typoed argument
comments in `codex-rs/linux-sandbox/src/landlock.rs`.
## What Changed
- Added `tools/argument-comment-lint/list-bazel-targets.sh` so Bazel
lint runs cover `//codex-rs/...` plus the manual `rust_test`
`*-unit-tests-bin` targets.
- Updated `just argument-comment-lint`, `rust-ci.yml`, and
`rust-ci-full.yml` to use that helper.
- Promoted both `argument-comment-mismatch` and
`uncommented-anonymous-literal-argument` to errors in every strict
entrypoint:
- `tools/argument-comment-lint/lint_aspect.bzl`
- `tools/argument-comment-lint/src/bin/argument-comment-lint.rs`
- `tools/argument-comment-lint/wrapper_common.py`
- Added wrapper/bin coverage for the stricter lint flags and documented
the behavior in `tools/argument-comment-lint/README.md`.
- Fixed the now-covered callsites in
`codex-rs/linux-sandbox/src/proxy_routing.rs`,
`codex-rs/linux-sandbox/src/landlock.rs`, and
`codex-rs/core/src/shell_snapshot_tests.rs`.
This keeps the Bazel target expansion narrow while making the Bazel and
prebuilt-linter paths enforce the same strict lint set.
## Verification
- `python3 -m unittest discover -s tools/argument-comment-lint -p
'test_*.py'`
- `cargo +nightly-2025-09-18 test --manifest-path
tools/argument-comment-lint/Cargo.toml`
- `just argument-comment-lint`
## Why
`#16193` moved the pure `tool_search` and `tool_suggest` spec builders
into `codex-tools`, but `codex-core` still owned the shared
discoverable-tool model that those builders and the `tool_suggest`
runtime both depend on. This change continues the migration by moving
that reusable model boundary out of `codex-core` as well, so the
discovery/suggestion stack uses one shared set of types and
`core/src/tools` no longer needs its own `discoverable.rs` module.
## What changed
- Moved `DiscoverableTool`, `DiscoverablePluginInfo`, and
`filter_tool_suggest_discoverable_tools_for_client()` into
`codex-rs/tools/src/tool_discovery.rs` alongside the extracted
discovery/suggestion spec builders.
- Added `codex-app-server-protocol` as a `codex-tools` dependency so the
shared discoverable-tool model can own the connector-side `AppInfo`
variant directly.
- Updated `core/src/tools/handlers/tool_suggest.rs`,
`core/src/tools/spec.rs`, `core/src/tools/router.rs`,
`core/src/connectors.rs`, and `core/src/codex.rs` to consume the shared
`codex-tools` model instead of the old core-local declarations.
- Changed `core/src/plugins/discoverable.rs` to return
`DiscoverablePluginInfo` directly, moved the pure client-filter coverage
into `tool_discovery_tests.rs`, and deleted the old
`core/src/tools/discoverable.rs` module.
- Updated `codex-rs/tools/README.md` so the crate boundary documents
that `codex-tools` now owns the discoverable-tool models in addition to
the discovery/suggestion spec builders.
## Test plan
- `cargo test -p codex-tools`
- `CARGO_TARGET_DIR=/tmp/codex-core-discoverable-model cargo test -p
codex-core --lib tools::handlers::tool_suggest::`
- `CARGO_TARGET_DIR=/tmp/codex-core-discoverable-model cargo test -p
codex-core --lib tools::spec::`
- `CARGO_TARGET_DIR=/tmp/codex-core-discoverable-model cargo test -p
codex-core --lib plugins::discoverable::`
- `just bazel-lock-check`
- `just argument-comment-lint`
## References
- #16193
- #16154
- #15923
- #15928
- #15944
- #15953
- #16031
- #16047
- #16129
- #16132
- #16138
- #16141
## Why
`core/src/tools/spec.rs` still owned the pure `tool_search` and
`tool_suggest` spec builders even though that logic no longer needed
`codex-core` runtime state. This change continues the `codex-tools`
migration by moving the reusable discovery and suggestion spec
construction out of `codex-core` so `spec.rs` is left with the
core-owned policy decisions about when these tools are exposed and what
metadata is available.
## What changed
- Added `codex-rs/tools/src/tool_discovery.rs` with the shared
`tool_search` and `tool_suggest` spec builders, plus focused unit tests
in `tool_discovery_tests.rs`.
- Moved the shared `DiscoverableToolAction` and `DiscoverableToolType`
declarations into `codex-tools` so the `tool_suggest` handler and the
extracted spec builders use the same wire-model enums.
- Updated `core/src/tools/spec.rs` to translate `ToolInfo` and
`DiscoverableTool` values into neutral `codex-tools` inputs and delegate
the actual spec building there.
- Removed the old template-based description rendering helpers from
`core/src/tools/spec.rs` and deleted the now-dead helper methods in
`core/src/tools/discoverable.rs`.
- Updated `codex-rs/tools/README.md` to document that discovery and
suggestion models/spec builders now live in `codex-tools`.
## Test plan
- `cargo test -p codex-tools`
- `CARGO_TARGET_DIR=/tmp/codex-core-discovery-specs cargo test -p
codex-core --lib tools::spec::`
- `CARGO_TARGET_DIR=/tmp/codex-core-discovery-specs cargo test -p
codex-core --lib tools::handlers::tool_suggest::`
- `just argument-comment-lint`
## References
- #16154
- #15923
- #15928
- #15944
- #15953
- #16031
- #16047
- #16129
- #16132
- #16138
- #16141