From 2aa8a2e11f5624c3d505308cec73fb6b2fa3fed1 Mon Sep 17 00:00:00 2001 From: Michael Bolin Date: Wed, 11 Feb 2026 21:54:48 -0800 Subject: [PATCH 1/3] ci: capture cargo timings in Rust CI and release workflows (#11543) ## 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. --- .github/workflows/rust-ci.yml | 20 ++++++++++++++++++-- .github/workflows/rust-release-windows.yml | 9 ++++++++- .github/workflows/rust-release.yml | 9 ++++++++- 3 files changed, 34 insertions(+), 4 deletions(-) diff --git a/.github/workflows/rust-ci.yml b/.github/workflows/rust-ci.yml index 436759cea4..f9c866e57c 100644 --- a/.github/workflows/rust-ci.yml +++ b/.github/workflows/rust-ci.yml @@ -380,7 +380,15 @@ jobs: cargo chef cook --recipe-path "$RECIPE" --target ${{ matrix.target }} --release --all-features - name: cargo clippy - run: cargo clippy --target ${{ matrix.target }} --all-features --tests --profile ${{ matrix.profile }} -- -D warnings + run: cargo clippy --target ${{ matrix.target }} --all-features --tests --profile ${{ matrix.profile }} --timings -- -D warnings + + - name: Upload Cargo timings (clippy) + if: always() + uses: actions/upload-artifact@v6 + with: + name: cargo-timings-rust-ci-clippy-${{ matrix.target }}-${{ matrix.profile }} + path: codex-rs/target/**/cargo-timings/cargo-timing.html + if-no-files-found: warn # Save caches explicitly; make non-fatal so cache packaging # never fails the overall job. Only save when key wasn't hit. @@ -567,11 +575,19 @@ jobs: - name: tests id: test - run: cargo nextest run --all-features --no-fail-fast --target ${{ matrix.target }} --cargo-profile ci-test + run: cargo nextest run --all-features --no-fail-fast --target ${{ matrix.target }} --cargo-profile ci-test --timings env: RUST_BACKTRACE: 1 NEXTEST_STATUS_LEVEL: leak + - name: Upload Cargo timings (nextest) + if: always() + uses: actions/upload-artifact@v6 + with: + name: cargo-timings-rust-ci-nextest-${{ matrix.target }}-${{ matrix.profile }} + path: codex-rs/target/**/cargo-timings/cargo-timing.html + if-no-files-found: warn + - name: Save cargo home cache if: always() && !cancelled() && steps.cache_cargo_home_restore.outputs.cache-hit != 'true' continue-on-error: true diff --git a/.github/workflows/rust-release-windows.yml b/.github/workflows/rust-release-windows.yml index 033027f5e2..fd411715be 100644 --- a/.github/workflows/rust-release-windows.yml +++ b/.github/workflows/rust-release-windows.yml @@ -89,7 +89,14 @@ jobs: - name: Cargo build (Windows binaries) shell: bash run: | - cargo build --target ${{ matrix.target }} --release ${{ matrix.build_args }} + cargo build --target ${{ matrix.target }} --release --timings ${{ matrix.build_args }} + + - name: Upload Cargo timings + uses: actions/upload-artifact@v6 + with: + name: cargo-timings-rust-release-windows-${{ matrix.target }}-${{ matrix.bundle }} + path: codex-rs/target/**/cargo-timings/cargo-timing.html + if-no-files-found: warn - name: Stage Windows binaries shell: bash diff --git a/.github/workflows/rust-release.yml b/.github/workflows/rust-release.yml index 34870f3032..7dca3bc70a 100644 --- a/.github/workflows/rust-release.yml +++ b/.github/workflows/rust-release.yml @@ -205,7 +205,14 @@ jobs: - name: Cargo build shell: bash run: | - cargo build --target ${{ matrix.target }} --release --bin codex --bin codex-responses-api-proxy + cargo build --target ${{ matrix.target }} --release --timings --bin codex --bin codex-responses-api-proxy + + - name: Upload Cargo timings + uses: actions/upload-artifact@v6 + with: + name: cargo-timings-rust-release-${{ matrix.target }} + path: codex-rs/target/**/cargo-timings/cargo-timing.html + if-no-files-found: warn - if: ${{ contains(matrix.target, 'linux') }} name: Cosign Linux artifacts From bd3ce981900288af224a1164adae0e321b330582 Mon Sep 17 00:00:00 2001 From: Gabriel Peal Date: Wed, 11 Feb 2026 22:04:17 -0800 Subject: [PATCH 2/3] Bump rmcp to 0.15 (#11539) https://github.com/modelcontextprotocol/rust-sdk/pull/598 in 0.14 broke some MCP oauth (like Linear) and https://github.com/modelcontextprotocol/rust-sdk/pull/641 fixed it in 0.15 --- codex-rs/Cargo.lock | 8 +++---- codex-rs/Cargo.toml | 2 +- codex-rs/core/src/codex.rs | 1 + codex-rs/core/src/mcp_connection_manager.rs | 21 +++++++++++++++++-- codex-rs/core/src/tools/spec.rs | 2 ++ .../src/posix/mcp_escalation_policy.rs | 2 +- codex-rs/exec-server/tests/common/lib.rs | 6 +++++- .../tests/suite/accept_elicitation.rs | 9 +++++++- .../exec-server/tests/suite/list_tools.rs | 1 + codex-rs/mcp-server/src/codex_tool_config.rs | 2 ++ codex-rs/mcp-server/src/message_processor.rs | 1 + .../mcp-server/tests/common/mcp_process.rs | 8 ++++++- codex-rs/rmcp-client/tests/resources.rs | 8 ++++++- 13 files changed, 59 insertions(+), 12 deletions(-) diff --git a/codex-rs/Cargo.lock b/codex-rs/Cargo.lock index 16616e334f..f2d48db34d 100644 --- a/codex-rs/Cargo.lock +++ b/codex-rs/Cargo.lock @@ -7395,9 +7395,9 @@ dependencies = [ [[package]] name = "rmcp" -version = "0.14.0" +version = "0.15.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0a621b37a548ff6ab6292d57841eb25785a7f146d89391a19c9f199414bd13da" +checksum = "1bef41ebc9ebed2c1b1d90203e9d1756091e8a00bbc3107676151f39868ca0ee" dependencies = [ "async-trait", "axum", @@ -7431,9 +7431,9 @@ dependencies = [ [[package]] name = "rmcp-macros" -version = "0.14.0" +version = "0.15.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6b79ed92303f9262db79575aa8c3652581668e9d136be6fd0b9ededa78954c95" +checksum = "0e88ad84b8b6237a934534a62b379a5be6388915663c0cc598ceb9b3292bbbfe" dependencies = [ "darling 0.23.0", "proc-macro2", diff --git a/codex-rs/Cargo.toml b/codex-rs/Cargo.toml index a615cdac2a..ab7e131b3e 100644 --- a/codex-rs/Cargo.toml +++ b/codex-rs/Cargo.toml @@ -214,7 +214,7 @@ ratatui-macros = "0.6.0" regex = "1.12.3" regex-lite = "0.1.8" reqwest = "0.12" -rmcp = { version = "0.14.0", default-features = false } +rmcp = { version = "0.15.0", default-features = false } runfiles = { git = "https://github.com/dzbarsky/rules_rust", rev = "b56cbaa8465e74127f1ea216f813cd377295ad81" } rustls = { version = "0.23", default-features = false, features = [ "ring", diff --git a/codex-rs/core/src/codex.rs b/codex-rs/core/src/codex.rs index 54dd34dbb4..9b7a393625 100644 --- a/codex-rs/core/src/codex.rs +++ b/codex-rs/core/src/codex.rs @@ -5393,6 +5393,7 @@ mod tests { input_schema: Arc::new(JsonObject::default()), output_schema: None, annotations: None, + execution: None, icons: None, meta: None, }, diff --git a/codex-rs/core/src/mcp_connection_manager.rs b/codex-rs/core/src/mcp_connection_manager.rs index 6f6bfad6e5..edd1e13f67 100644 --- a/codex-rs/core/src/mcp_connection_manager.rs +++ b/codex-rs/core/src/mcp_connection_manager.rs @@ -43,7 +43,9 @@ use futures::future::BoxFuture; use futures::future::FutureExt; use futures::future::Shared; use rmcp::model::ClientCapabilities; +use rmcp::model::CreateElicitationRequestParams; use rmcp::model::ElicitationCapability; +use rmcp::model::FormElicitationCapability; use rmcp::model::Implementation; use rmcp::model::InitializeRequestParams; use rmcp::model::ListResourceTemplatesResult; @@ -223,7 +225,16 @@ impl ElicitationRequestManager { ProtocolRequestId::Integer(value) } }, - message: elicitation.message, + message: match elicitation { + CreateElicitationRequestParams::FormElicitationParams { + message, + .. + } + | CreateElicitationRequestParams::UrlElicitationParams { + message, + .. + } => message, + }, }), }) .await; @@ -985,12 +996,16 @@ async fn start_server_task( meta: None, capabilities: ClientCapabilities { experimental: None, + extensions: None, roots: None, sampling: None, // https://modelcontextprotocol.io/specification/2025-06-18/client/elicitation#capabilities // indicates this should be an empty object. elicitation: Some(ElicitationCapability { - schema_validation: None, + form: Some(FormElicitationCapability { + schema_validation: None, + }), + url: None, }), tasks: None, }, @@ -998,6 +1013,7 @@ async fn start_server_task( name: "codex-mcp-client".to_owned(), version: env!("CARGO_PKG_VERSION").to_owned(), title: Some("Codex".into()), + description: None, icons: None, website_url: None, }, @@ -1247,6 +1263,7 @@ mod tests { input_schema: Arc::new(JsonObject::default()), output_schema: None, annotations: None, + execution: None, icons: None, meta: None, }, diff --git a/codex-rs/core/src/tools/spec.rs b/codex-rs/core/src/tools/spec.rs index 098f640690..b8618e0c07 100644 --- a/codex-rs/core/src/tools/spec.rs +++ b/codex-rs/core/src/tools/spec.rs @@ -1624,6 +1624,7 @@ mod tests { input_schema: std::sync::Arc::new(rmcp::model::object(input_schema)), output_schema: None, annotations: None, + execution: None, icons: None, meta: None, } @@ -1641,6 +1642,7 @@ mod tests { input_schema: std::sync::Arc::new(schema), output_schema: None, annotations: None, + execution: None, icons: None, meta: None, }; diff --git a/codex-rs/exec-server/src/posix/mcp_escalation_policy.rs b/codex-rs/exec-server/src/posix/mcp_escalation_policy.rs index 5ef1e60178..5e6b97f1ad 100644 --- a/codex-rs/exec-server/src/posix/mcp_escalation_policy.rs +++ b/codex-rs/exec-server/src/posix/mcp_escalation_policy.rs @@ -69,7 +69,7 @@ impl McpEscalationPolicy { .pause_for(async { context .peer - .create_elicitation(CreateElicitationRequestParams { + .create_elicitation(CreateElicitationRequestParams::FormElicitationParams { meta: None, message: format!( "Allow agent to run `{command}` in `{}`?", diff --git a/codex-rs/exec-server/tests/common/lib.rs b/codex-rs/exec-server/tests/common/lib.rs index 430eb89953..ce9fa05216 100644 --- a/codex-rs/exec-server/tests/common/lib.rs +++ b/codex-rs/exec-server/tests/common/lib.rs @@ -166,7 +166,11 @@ impl ClientHandler for InteractiveClient { .unwrap() .push(request.clone()); - let accept = self.elicitations_to_accept.contains(&request.message); + let message = match &request { + CreateElicitationRequestParams::FormElicitationParams { message, .. } + | CreateElicitationRequestParams::UrlElicitationParams { message, .. } => message, + }; + let accept = self.elicitations_to_accept.contains(message); async move { if accept { Ok(CreateElicitationResult { diff --git a/codex-rs/exec-server/tests/suite/accept_elicitation.rs b/codex-rs/exec-server/tests/suite/accept_elicitation.rs index 7cb7438a51..365a93b837 100644 --- a/codex-rs/exec-server/tests/suite/accept_elicitation.rs +++ b/codex-rs/exec-server/tests/suite/accept_elicitation.rs @@ -138,7 +138,14 @@ prefix_rule( .lock() .unwrap() .iter() - .map(|r| r.message.clone()) + .map(|r| match r { + rmcp::model::CreateElicitationRequestParams::FormElicitationParams { + message, .. + } + | rmcp::model::CreateElicitationRequestParams::UrlElicitationParams { + message, .. + } => message.clone(), + }) .collect::>(); assert_eq!(vec![expected_elicitation_message], elicitation_messages); diff --git a/codex-rs/exec-server/tests/suite/list_tools.rs b/codex-rs/exec-server/tests/suite/list_tools.rs index 4a28dec5b3..614e8d6d35 100644 --- a/codex-rs/exec-server/tests/suite/list_tools.rs +++ b/codex-rs/exec-server/tests/suite/list_tools.rs @@ -67,6 +67,7 @@ async fn list_tools() -> Result<()> { "type": "object", }))), output_schema: None, + execution: None, annotations: None, icons: None, meta: None diff --git a/codex-rs/mcp-server/src/codex_tool_config.rs b/codex-rs/mcp-server/src/codex_tool_config.rs index 94bf4369a9..cf34dc280e 100644 --- a/codex-rs/mcp-server/src/codex_tool_config.rs +++ b/codex-rs/mcp-server/src/codex_tool_config.rs @@ -127,6 +127,7 @@ pub(crate) fn create_tool_for_codex_tool_call_param() -> Tool { .into(), ), annotations: None, + execution: None, icons: None, meta: None, } @@ -248,6 +249,7 @@ pub(crate) fn create_tool_for_codex_tool_call_reply_param() -> Tool { "Continue a Codex conversation by providing the thread id and prompt.".into(), ), annotations: None, + execution: None, icons: None, meta: None, } diff --git a/codex-rs/mcp-server/src/message_processor.rs b/codex-rs/mcp-server/src/message_processor.rs index c3449d50c2..612582dae4 100644 --- a/codex-rs/mcp-server/src/message_processor.rs +++ b/codex-rs/mcp-server/src/message_processor.rs @@ -209,6 +209,7 @@ impl MessageProcessor { name: "codex-mcp-server".to_string(), title: Some("Codex".to_string()), version: env!("CARGO_PKG_VERSION").to_string(), + description: None, icons: None, website_url: None, }; diff --git a/codex-rs/mcp-server/tests/common/mcp_process.rs b/codex-rs/mcp-server/tests/common/mcp_process.rs index 34c5380818..92b5caa65a 100644 --- a/codex-rs/mcp-server/tests/common/mcp_process.rs +++ b/codex-rs/mcp-server/tests/common/mcp_process.rs @@ -18,6 +18,7 @@ use rmcp::model::ClientCapabilities; use rmcp::model::CustomNotification; use rmcp::model::CustomRequest; use rmcp::model::ElicitationCapability; +use rmcp::model::FormElicitationCapability; use rmcp::model::Implementation; use rmcp::model::InitializeRequestParams; use rmcp::model::JsonRpcMessage; @@ -116,9 +117,13 @@ impl McpProcess { meta: None, capabilities: ClientCapabilities { elicitation: Some(ElicitationCapability { - schema_validation: None, + form: Some(FormElicitationCapability { + schema_validation: None, + }), + url: None, }), experimental: None, + extensions: None, roots: None, sampling: None, tasks: None, @@ -127,6 +132,7 @@ impl McpProcess { name: "elicitation test".into(), title: Some("Elicitation Test".into()), version: "0.0.0".into(), + description: None, icons: None, website_url: None, }, diff --git a/codex-rs/rmcp-client/tests/resources.rs b/codex-rs/rmcp-client/tests/resources.rs index efab352ff3..9e357bfcbd 100644 --- a/codex-rs/rmcp-client/tests/resources.rs +++ b/codex-rs/rmcp-client/tests/resources.rs @@ -10,6 +10,7 @@ use futures::FutureExt as _; use rmcp::model::AnnotateAble; use rmcp::model::ClientCapabilities; use rmcp::model::ElicitationCapability; +use rmcp::model::FormElicitationCapability; use rmcp::model::Implementation; use rmcp::model::InitializeRequestParams; use rmcp::model::ListResourceTemplatesResult; @@ -29,10 +30,14 @@ fn init_params() -> InitializeRequestParams { meta: None, capabilities: ClientCapabilities { experimental: None, + extensions: None, roots: None, sampling: None, elicitation: Some(ElicitationCapability { - schema_validation: None, + form: Some(FormElicitationCapability { + schema_validation: None, + }), + url: None, }), tasks: None, }, @@ -40,6 +45,7 @@ fn init_params() -> InitializeRequestParams { name: "codex-test".into(), version: "0.0.0-test".into(), title: Some("Codex rmcp resource test".into()), + description: None, icons: None, website_url: None, }, From 6a9416d588b1a82a91520c02b8b230b341d64d86 Mon Sep 17 00:00:00 2001 From: Michael Bolin Date: Wed, 11 Feb 2026 23:41:30 -0800 Subject: [PATCH 3/3] Fix linux-musl release link failures caused by glibc-only libcap artifacts 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 --- .github/scripts/install-musl-build-tools.sh | 48 ++++++++++++++++++++- 1 file changed, 47 insertions(+), 1 deletion(-) diff --git a/.github/scripts/install-musl-build-tools.sh b/.github/scripts/install-musl-build-tools.sh index bd392b8548..721f975a39 100644 --- a/.github/scripts/install-musl-build-tools.sh +++ b/.github/scripts/install-musl-build-tools.sh @@ -17,7 +17,7 @@ if [[ -n "${APT_INSTALL_ARGS:-}" ]]; then fi sudo apt-get update "${apt_update_args[@]}" -sudo apt-get install -y "${apt_install_args[@]}" musl-tools pkg-config libcap-dev g++ clang libc++-dev libc++abi-dev lld +sudo apt-get install -y "${apt_install_args[@]}" ca-certificates curl musl-tools pkg-config libcap-dev g++ clang libc++-dev libc++abi-dev lld xz-utils case "${TARGET}" in x86_64-unknown-linux-musl) @@ -32,6 +32,11 @@ case "${TARGET}" in ;; esac +libcap_version="2.75" +libcap_sha256="de4e7e064c9ba451d5234dd46e897d7c71c96a9ebf9a0c445bc04f4742d83632" +libcap_tarball_name="libcap-${libcap_version}.tar.xz" +libcap_download_url="https://mirrors.edge.kernel.org/pub/linux/libs/security/linux-privs/libcap2/${libcap_tarball_name}" + # Use the musl toolchain as the Rust linker to avoid Zig injecting its own CRT. if command -v "${arch}-linux-musl-gcc" >/dev/null; then musl_linker="$(command -v "${arch}-linux-musl-gcc")" @@ -47,6 +52,43 @@ runner_temp="${RUNNER_TEMP:-/tmp}" tool_root="${runner_temp}/codex-musl-tools-${TARGET}" mkdir -p "${tool_root}" +libcap_root="${tool_root}/libcap-${libcap_version}" +libcap_src_root="${libcap_root}/src" +libcap_prefix="${libcap_root}/prefix" +libcap_pkgconfig_dir="${libcap_prefix}/lib/pkgconfig" + +if [[ ! -f "${libcap_prefix}/lib/libcap.a" ]]; then + mkdir -p "${libcap_src_root}" "${libcap_prefix}/lib" "${libcap_prefix}/include/sys" "${libcap_prefix}/include/linux" "${libcap_pkgconfig_dir}" + libcap_tarball="${libcap_root}/${libcap_tarball_name}" + + curl -fsSL "${libcap_download_url}" -o "${libcap_tarball}" + echo "${libcap_sha256} ${libcap_tarball}" | sha256sum -c - + + tar -xJf "${libcap_tarball}" -C "${libcap_src_root}" + libcap_source_dir="${libcap_src_root}/libcap-${libcap_version}" + make -C "${libcap_source_dir}/libcap" -j"$(nproc)" \ + CC="${musl_linker}" \ + AR=ar \ + RANLIB=ranlib + + cp "${libcap_source_dir}/libcap/libcap.a" "${libcap_prefix}/lib/libcap.a" + cp "${libcap_source_dir}/libcap/include/uapi/linux/capability.h" "${libcap_prefix}/include/linux/capability.h" + cp "${libcap_source_dir}/libcap/../libcap/include/sys/capability.h" "${libcap_prefix}/include/sys/capability.h" + + cat > "${libcap_pkgconfig_dir}/libcap.pc" </dev/null; then zig_bin="$(command -v zig)" @@ -220,6 +262,10 @@ echo "CMAKE_ARGS=-DCMAKE_HAVE_THREADS_LIBRARY=1 -DCMAKE_USE_PTHREADS_INIT=1 -DCM # Allow pkg-config resolution during cross-compilation. echo "PKG_CONFIG_ALLOW_CROSS=1" >> "$GITHUB_ENV" +echo "PKG_CONFIG_PATH=${libcap_pkgconfig_dir}\${PKG_CONFIG_PATH:+:\${PKG_CONFIG_PATH}}" >> "$GITHUB_ENV" +pkg_config_path_var="PKG_CONFIG_PATH_${TARGET}" +pkg_config_path_var="${pkg_config_path_var//-/_}" +echo "${pkg_config_path_var}=${libcap_pkgconfig_dir}" >> "$GITHUB_ENV" if [[ -n "${sysroot}" && "${sysroot}" != "/" ]]; then echo "PKG_CONFIG_SYSROOT_DIR=${sysroot}" >> "$GITHUB_ENV"