From 702238f0043b3bbc95bd97ba5131eea6e1160e5a Mon Sep 17 00:00:00 2001 From: Akrelion45 Date: Mon, 17 Nov 2025 04:15:06 +0100 Subject: [PATCH 1/8] Fix AltGr/backslash input on Windows Codex terminal (#6720) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ### Summary - Treat AltGr chords (Ctrl+Alt) as literal character input in the Codex TUI textarea so Windows terminals that report backslash and other characters via AltGr insert correctly. - Add regression test altgr_ctrl_alt_char_inserts_literal to ensure Ctrl+Alt char events append the character and advance the cursor. ### Motivation On US/UK keyboard layouts, backslash is produced by a plain key, so Ctrl+Alt handling is never exercised and the bug isn’t visible. On many non‑US layouts (e.g., German), backslash and other symbols require AltGr, which terminals report as Ctrl+Alt+. Our textarea previously filtered these chords like navigation bindings, so AltGr input was dropped on affected layouts. This change treats AltGr chords as literal input so backslash and similar symbols work on Windows terminals. This fixes multiple reported Issues where the \ symbol got cut off. Like: C:\Users\Admin became C:UsersAdmin Co-authored-by: Eric Traut --- codex-rs/tui/src/bottom_pane/textarea.rs | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/codex-rs/tui/src/bottom_pane/textarea.rs b/codex-rs/tui/src/bottom_pane/textarea.rs index cd913b00d4..0a7faa4fcb 100644 --- a/codex-rs/tui/src/bottom_pane/textarea.rs +++ b/codex-rs/tui/src/bottom_pane/textarea.rs @@ -247,6 +247,16 @@ impl TextArea { } if modifiers == (KeyModifiers::CONTROL | KeyModifiers::ALT) => { self.delete_backward_word() }, + KeyEvent { + code: KeyCode::Char(c), + modifiers, + .. + } if modifiers.contains(KeyModifiers::ALT) + && modifiers.contains(KeyModifiers::CONTROL) => + { + // AltGr on many keyboards reports as Ctrl+Alt; treat it as a literal char. + self.insert_str(&c.to_string()); + }, KeyEvent { code: KeyCode::Backspace, modifiers: KeyModifiers::ALT, @@ -1454,6 +1464,17 @@ mod tests { assert_eq!(t.cursor(), 3); } + #[test] + fn altgr_ctrl_alt_char_inserts_literal() { + let mut t = ta_with(""); + t.input(KeyEvent::new( + KeyCode::Char('c'), + KeyModifiers::CONTROL | KeyModifiers::ALT, + )); + assert_eq!(t.text(), "c"); + assert_eq!(t.cursor(), 1); + } + #[test] fn cursor_vertical_movement_across_lines_and_bounds() { let mut t = ta_with("short\nloooooooooong\nmid"); From de1768d3ba67322478f02a343ac7ddd9dd3b0925 Mon Sep 17 00:00:00 2001 From: dulikaifazr Date: Mon, 17 Nov 2025 11:50:36 +0800 Subject: [PATCH 2/8] Fix: Claude models return incomplete responses due to empty finish_reason handling (#6728) ## Summary Fixes streaming issue where Claude models return only 1-4 characters instead of full responses when used through certain API providers/proxies. ## Environment - **OS**: Windows - **Models affected**: Claude models (e.g., claude-haiku-4-5-20251001) - **API Provider**: AAAI API proxy (https://api.aaai.vip/v1) - **Working models**: GLM, Google models work correctly ## Problem When using Claude models in both TUI and exec modes, only 1-4 characters are displayed despite the backend receiving the full response. Debug logs revealed that some API providers send SSE chunks with an empty string finish_reason during active streaming, rather than null or omitting the field entirely. The current code treats any non-null finish_reason as a termination signal, causing the stream to exit prematurely after the first chunk. The problematic chunks contain finish_reason with an empty string instead of null. ## Solution Fix empty finish_reason handling in chat_completions.rs by adding a check to only process non-empty finish_reason values. This ensures empty strings are ignored and streaming continues normally. ## Testing - Tested on Windows with Claude Haiku model via AAAI API proxy - Full responses now received and displayed correctly in both TUI and exec modes - Other models (GLM, Google) continue to work as expected - No regression in existing functionality ## Impact - Improves compatibility with API providers that send empty finish_reason during streaming - Enables Claude models to work correctly in Windows environment - No breaking changes to existing functionality ## Related Issues This fix resolves the issue where Claude models appeared to return incomplete responses. The root cause was identified as a compatibility issue in parsing SSE responses from certain API providers/proxies, rather than a model-specific problem. This change improves overall robustness when working with various API endpoints. --------- Co-authored-by: Eric Traut --- codex-rs/core/src/chat_completions.rs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/codex-rs/core/src/chat_completions.rs b/codex-rs/core/src/chat_completions.rs index 5b9578e759..a60db89d83 100644 --- a/codex-rs/core/src/chat_completions.rs +++ b/codex-rs/core/src/chat_completions.rs @@ -673,7 +673,9 @@ async fn process_chat_sse( } // Emit end-of-turn when finish_reason signals completion. - if let Some(finish_reason) = choice.get("finish_reason").and_then(|v| v.as_str()) { + if let Some(finish_reason) = choice.get("finish_reason").and_then(|v| v.as_str()) + && !finish_reason.is_empty() + { match finish_reason { "tool_calls" if fn_call_state.active => { // First, flush the terminal raw reasoning so UIs can finalize From e70c52a3af8710b7e6fd041840c74f01d3a5eb94 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Sun, 16 Nov 2025 19:53:19 -0800 Subject: [PATCH 3/8] chore(deps): bump actions/github-script from 7 to 8 (#6755) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Bumps [actions/github-script](https://github.com/actions/github-script) from 7 to 8.
Release notes

Sourced from actions/github-script's releases.

v8.0.0

What's Changed

⚠️ Minimum Compatible Runner Version

v2.327.1
Release Notes

Make sure your runner is updated to this version or newer to use this release.

New Contributors

Full Changelog: https://github.com/actions/github-script/compare/v7.1.0...v8.0.0

v7.1.0

What's Changed

New Contributors

Full Changelog: https://github.com/actions/github-script/compare/v7...v7.1.0

... (truncated)

Commits

[![Dependabot compatibility score](https://dependabot-badges.githubapp.com/badges/compatibility_score?dependency-name=actions/github-script&package-manager=github_actions&previous-version=7&new-version=8)](https://docs.github.com/en/github/managing-security-vulnerabilities/about-dependabot-security-updates#about-compatibility-scores) Dependabot will resolve any conflicts with this PR as long as you don't alter it yourself. You can also trigger a rebase manually by commenting `@dependabot rebase`. [//]: # (dependabot-automerge-start) [//]: # (dependabot-automerge-end) ---
Dependabot commands and options
You can trigger Dependabot actions by commenting on this PR: - `@dependabot rebase` will rebase this PR - `@dependabot recreate` will recreate this PR, overwriting any edits that have been made to it - `@dependabot merge` will merge this PR after your CI passes on it - `@dependabot squash and merge` will squash and merge this PR after your CI passes on it - `@dependabot cancel merge` will cancel a previously requested merge and block automerging - `@dependabot reopen` will reopen this PR if it is closed - `@dependabot close` will close this PR and stop Dependabot recreating it. You can achieve the same result by closing it manually - `@dependabot show ignore conditions` will show all of the ignore conditions of the specified dependency - `@dependabot ignore this major version` will close this PR and stop Dependabot creating any more for this major version (unless you reopen the PR or upgrade to it yourself) - `@dependabot ignore this minor version` will close this PR and stop Dependabot creating any more for this minor version (unless you reopen the PR or upgrade to it yourself) - `@dependabot ignore this dependency` will close this PR and stop Dependabot creating any more for this dependency (unless you reopen the PR or upgrade to it yourself)
Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/close-stale-contributor-prs.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/close-stale-contributor-prs.yml b/.github/workflows/close-stale-contributor-prs.yml index b3cb7fb44e..e01bc3881d 100644 --- a/.github/workflows/close-stale-contributor-prs.yml +++ b/.github/workflows/close-stale-contributor-prs.yml @@ -15,7 +15,7 @@ jobs: runs-on: ubuntu-latest steps: - name: Close inactive PRs from contributors - uses: actions/github-script@v7 + uses: actions/github-script@v8 with: github-token: ${{ secrets.GITHUB_TOKEN }} script: | From a52cf4d2b44bcb136f8597b74da65cf66d385e2b Mon Sep 17 00:00:00 2001 From: Eric Traut Date: Sun, 16 Nov 2025 22:49:31 -0600 Subject: [PATCH 4/8] Exempt the "codex" github user from signing the CLA (#6724) This fixes bug #6697 --- .github/workflows/cla.yml | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/.github/workflows/cla.yml b/.github/workflows/cla.yml index bd70e9a81e..17d54f214a 100644 --- a/.github/workflows/cla.yml +++ b/.github/workflows/cla.yml @@ -46,4 +46,6 @@ jobs: path-to-document: https://github.com/openai/codex/blob/main/docs/CLA.md path-to-signatures: signatures/cla.json branch: cla-signatures - allowlist: dependabot[bot] + allowlist: | + codex + dependabot[bot] From 5860481bc464ef89c7ccc353fe30449cabf5ffd0 Mon Sep 17 00:00:00 2001 From: Xiao-Yong Jin Date: Sun, 16 Nov 2025 23:07:34 -0600 Subject: [PATCH 5/8] Fix FreeBSD/OpenBSD builds: target-specific keyring features and BSD hardening (#6680) ## Summary Builds on FreeBSD and OpenBSD were failing due to globally enabled Linux-specific keyring features and hardening code paths not gated by OS. This PR scopes keyring native backends to the appropriate targets, disables default features at the workspace root, and adds a BSD-specific hardening function. Linux/macOS/Windows behavior remains unchanged, while FreeBSD/OpenBSD now build and run with a supported backend. ## Key Changes - Keyring features: - Disable keyring default features at the workspace root to avoid pulling Linux backends on non-Linux. - Move native backend features into target-specific sections in the affected crates: - Linux: linux-native-async-persistent - macOS: apple-native - Windows: windows-native - FreeBSD/OpenBSD: sync-secret-service - Process hardening: - Add pre_main_hardening_bsd() for FreeBSD/OpenBSD, applying: - Set RLIMIT_CORE to 0 - Clear LD_* environment variables - Simplify process-hardening Cargo deps to unconditional libc (avoid conflicting OS fragments). - No changes to CODEX_SANDBOX_* behavior. ## Rationale - Previously, enabling keyring native backends globally pulled Linux-only features on BSD, causing build errors. - Hardening logic was tailored for Linux/macOS; BSD builds lacked a gated path with equivalent safeguards. - Target-scoped features and BSD hardening make the crates portable across these OSes without affecting existing behavior elsewhere. ## Impact by Platform - Linux: No functional change; backends now selected via target cfg. - macOS: No functional change; explicit apple-native mapping. - Windows: No functional change; explicit windows-native mapping. - FreeBSD/OpenBSD: Builds succeed using sync-secret-service; BSD hardening applied during startup. ## Testing - Verified compilation across affected crates with target-specific features. - Smoke-checked that Linux/macOS/Windows feature sets remain identical functionally after scoping. - On BSD, confirmed keyring resolves to sync-secret-service and hardening compiles. ## Risks / Compatibility - Minimal risk: only feature scoping and OS-gated additions. - No public API changes in the crates; runtime behavior on non-BSD platforms is preserved. - On BSD, the new hardening clears LD_*; this is consistent with security posture on other Unix platforms. ## Reviewer Notes - Pay attention to target-specific sections for keyring in the affected Cargo.toml files. - Confirm pre_main_hardening_bsd() mirrors the safe subset of Linux/macOS hardening without introducing Linux-only calls. - Confirm no references to CODEX_SANDBOX_ENV_VAR or CODEX_SANDBOX_NETWORK_DISABLED_ENV_VAR were added/modified. ## Checklist - Disable keyring default features at workspace root. - Target-specific keyring features mapped per OS (Linux/macOS/Windows/BSD). - Add BSD hardening (RLIMIT_CORE=0, clear LD_*). - Simplify process-hardening dependencies to unconditional libc. - No changes to sandbox env var code. - Formatting and linting: just fmt + just fix -p for changed crates. - Project tests pass for changed crates; broader suite unchanged. --------- Co-authored-by: celia-oai --- codex-rs/Cargo.toml | 2 +- codex-rs/core/Cargo.toml | 15 +++++++----- codex-rs/keyring-store/Cargo.toml | 19 ++++++++++----- codex-rs/process-hardening/Cargo.toml | 7 ------ codex-rs/process-hardening/src/lib.rs | 33 ++++++++++++++++++++++++++- codex-rs/rmcp-client/Cargo.toml | 18 ++++++++++----- 6 files changed, 67 insertions(+), 27 deletions(-) diff --git a/codex-rs/Cargo.toml b/codex-rs/Cargo.toml index 3460faf5d7..7c905cc723 100644 --- a/codex-rs/Cargo.toml +++ b/codex-rs/Cargo.toml @@ -130,7 +130,7 @@ image = { version = "^0.25.8", default-features = false } indexmap = "2.12.0" insta = "1.43.2" itertools = "0.14.0" -keyring = "3.6" +keyring = { version = "3.6", default-features = false } landlock = "0.4.1" lazy_static = "1" libc = "0.2.175" diff --git a/codex-rs/core/Cargo.toml b/codex-rs/core/Cargo.toml index ab732c910c..4d8f43778c 100644 --- a/codex-rs/core/Cargo.toml +++ b/codex-rs/core/Cargo.toml @@ -40,12 +40,7 @@ eventsource-stream = { workspace = true } futures = { workspace = true } http = { workspace = true } indexmap = { workspace = true } -keyring = { workspace = true, features = [ - "apple-native", - "crypto-rust", - "linux-native-async-persistent", - "windows-native", -] } +keyring = { workspace = true, features = ["crypto-rust"] } libc = { workspace = true } mcp-types = { workspace = true } os_info = { workspace = true } @@ -90,9 +85,11 @@ wildmatch = { workspace = true } [target.'cfg(target_os = "linux")'.dependencies] landlock = { workspace = true } seccompiler = { workspace = true } +keyring = { workspace = true, features = ["linux-native-async-persistent"] } [target.'cfg(target_os = "macos")'.dependencies] core-foundation = "0.9" +keyring = { workspace = true, features = ["apple-native"] } # Build OpenSSL from source for musl builds. [target.x86_64-unknown-linux-musl.dependencies] @@ -102,6 +99,12 @@ openssl-sys = { workspace = true, features = ["vendored"] } [target.aarch64-unknown-linux-musl.dependencies] openssl-sys = { workspace = true, features = ["vendored"] } +[target.'cfg(target_os = "windows")'.dependencies] +keyring = { workspace = true, features = ["windows-native"] } + +[target.'cfg(any(target_os = "freebsd", target_os = "openbsd"))'.dependencies] +keyring = { workspace = true, features = ["sync-secret-service"] } + [dev-dependencies] assert_cmd = { workspace = true } assert_matches = { workspace = true } diff --git a/codex-rs/keyring-store/Cargo.toml b/codex-rs/keyring-store/Cargo.toml index f662e5d4ff..932693de50 100644 --- a/codex-rs/keyring-store/Cargo.toml +++ b/codex-rs/keyring-store/Cargo.toml @@ -7,10 +7,17 @@ version = { workspace = true } workspace = true [dependencies] -keyring = { workspace = true, features = [ - "apple-native", - "crypto-rust", - "linux-native-async-persistent", - "windows-native", -] } +keyring = { workspace = true, features = ["crypto-rust"] } tracing = { workspace = true } + +[target.'cfg(target_os = "linux")'.dependencies] +keyring = { workspace = true, features = ["linux-native-async-persistent"] } + +[target.'cfg(target_os = "macos")'.dependencies] +keyring = { workspace = true, features = ["apple-native"] } + +[target.'cfg(target_os = "windows")'.dependencies] +keyring = { workspace = true, features = ["windows-native"] } + +[target.'cfg(any(target_os = "freebsd", target_os = "openbsd"))'.dependencies] +keyring = { workspace = true, features = ["sync-secret-service"] } diff --git a/codex-rs/process-hardening/Cargo.toml b/codex-rs/process-hardening/Cargo.toml index 7294b6e268..2ba4b0d5ca 100644 --- a/codex-rs/process-hardening/Cargo.toml +++ b/codex-rs/process-hardening/Cargo.toml @@ -11,11 +11,4 @@ path = "src/lib.rs" workspace = true [dependencies] -[target.'cfg(target_os = "linux")'.dependencies] -libc = { workspace = true } - -[target.'cfg(target_os = "android")'.dependencies] -libc = { workspace = true } - -[target.'cfg(target_os = "macos")'.dependencies] libc = { workspace = true } diff --git a/codex-rs/process-hardening/src/lib.rs b/codex-rs/process-hardening/src/lib.rs index a787b4097d..0a624fb387 100644 --- a/codex-rs/process-hardening/src/lib.rs +++ b/codex-rs/process-hardening/src/lib.rs @@ -10,6 +10,10 @@ pub fn pre_main_hardening() { #[cfg(target_os = "macos")] pre_main_hardening_macos(); + // On FreeBSD and OpenBSD, apply similar hardening to Linux/macOS: + #[cfg(any(target_os = "freebsd", target_os = "openbsd"))] + pre_main_hardening_bsd(); + #[cfg(windows)] pre_main_hardening_windows(); } @@ -20,7 +24,13 @@ const PRCTL_FAILED_EXIT_CODE: i32 = 5; #[cfg(target_os = "macos")] const PTRACE_DENY_ATTACH_FAILED_EXIT_CODE: i32 = 6; -#[cfg(any(target_os = "linux", target_os = "android", target_os = "macos"))] +#[cfg(any( + target_os = "linux", + target_os = "android", + target_os = "macos", + target_os = "freebsd", + target_os = "openbsd" +))] const SET_RLIMIT_CORE_FAILED_EXIT_CODE: i32 = 7; #[cfg(any(target_os = "linux", target_os = "android"))] @@ -57,6 +67,27 @@ pub(crate) fn pre_main_hardening_linux() { } } +#[cfg(any(target_os = "freebsd", target_os = "openbsd"))] +pub(crate) fn pre_main_hardening_bsd() { + // FreeBSD/OpenBSD: set RLIMIT_CORE to 0 and clear LD_* env vars + set_core_file_size_limit_to_zero(); + + let ld_keys: Vec = std::env::vars() + .filter_map(|(key, _)| { + if key.starts_with("LD_") { + Some(key) + } else { + None + } + }) + .collect(); + for key in ld_keys { + unsafe { + std::env::remove_var(key); + } + } +} + #[cfg(target_os = "macos")] pub(crate) fn pre_main_hardening_macos() { // Prevent debuggers from attaching to this process. diff --git a/codex-rs/rmcp-client/Cargo.toml b/codex-rs/rmcp-client/Cargo.toml index 68ef4509b3..5c3f1dc0c8 100644 --- a/codex-rs/rmcp-client/Cargo.toml +++ b/codex-rs/rmcp-client/Cargo.toml @@ -16,12 +16,7 @@ codex-keyring-store = { workspace = true } codex-protocol = { workspace = true } dirs = { workspace = true } futures = { workspace = true, default-features = false, features = ["std"] } -keyring = { workspace = true, features = [ - "apple-native", - "crypto-rust", - "linux-native-async-persistent", - "windows-native", -] } +keyring = { workspace = true, features = ["crypto-rust"] } mcp-types = { path = "../mcp-types" } oauth2 = "5" reqwest = { version = "0.12", default-features = false, features = [ @@ -63,3 +58,14 @@ escargot = { workspace = true } pretty_assertions = { workspace = true } serial_test = { workspace = true } tempfile = { workspace = true } +[target.'cfg(target_os = "linux")'.dependencies] +keyring = { workspace = true, features = ["linux-native-async-persistent"] } + +[target.'cfg(target_os = "macos")'.dependencies] +keyring = { workspace = true, features = ["apple-native"] } + +[target.'cfg(target_os = "windows")'.dependencies] +keyring = { workspace = true, features = ["windows-native"] } + +[target.'cfg(any(target_os = "freebsd", target_os = "openbsd"))'.dependencies] +keyring = { workspace = true, features = ["sync-secret-service"] } From 497fb4a19c3717e4e17a5a25987643fe667d8a5b Mon Sep 17 00:00:00 2001 From: Dylan Hurd Date: Sun, 16 Nov 2025 23:16:51 -0800 Subject: [PATCH 6/8] fix(core) serialize shell_command (#6744) ## Summary Ensures we're serializing calls to `shell_command` ## Testing - [x] Added unit test --- codex-rs/core/src/client_common.rs | 2 +- codex-rs/core/src/tools/parallel.rs | 2 +- .../core/tests/suite/shell_serialization.rs | 53 +++++++++++++++++++ 3 files changed, 55 insertions(+), 2 deletions(-) diff --git a/codex-rs/core/src/client_common.rs b/codex-rs/core/src/client_common.rs index a628e0d320..9494ffcdf4 100644 --- a/codex-rs/core/src/client_common.rs +++ b/codex-rs/core/src/client_common.rs @@ -136,7 +136,7 @@ fn reserialize_shell_outputs(items: &mut [ResponseItem]) { } fn is_shell_tool_name(name: &str) -> bool { - matches!(name, "shell" | "container.exec") + matches!(name, "shell" | "container.exec" | "shell_command") } #[derive(Deserialize)] diff --git a/codex-rs/core/src/tools/parallel.rs b/codex-rs/core/src/tools/parallel.rs index 56a4547526..33dc42b936 100644 --- a/codex-rs/core/src/tools/parallel.rs +++ b/codex-rs/core/src/tools/parallel.rs @@ -112,7 +112,7 @@ impl ToolCallRuntime { fn abort_message(call: &ToolCall, secs: f32) -> String { match call.tool_name.as_str() { - "shell" | "container.exec" | "local_shell" | "unified_exec" => { + "shell" | "container.exec" | "local_shell" | "shell_command" | "unified_exec" => { format!("Wall time: {secs:.1} seconds\naborted by user") } _ => format!("aborted by user after {secs:.1}s"), diff --git a/codex-rs/core/tests/suite/shell_serialization.rs b/codex-rs/core/tests/suite/shell_serialization.rs index 237b2db377..44d637b9e5 100644 --- a/codex-rs/core/tests/suite/shell_serialization.rs +++ b/codex-rs/core/tests/suite/shell_serialization.rs @@ -788,6 +788,59 @@ Output: Ok(()) } +#[tokio::test(flavor = "multi_thread", worker_threads = 2)] +async fn shell_command_output_is_structured() -> Result<()> { + skip_if_no_network!(Ok(())); + + let server = start_mock_server().await; + let mut builder = test_codex().with_config(|config| { + config.features.enable(Feature::ShellCommandTool); + }); + let test = builder.build(&server).await?; + + let call_id = "shell-command"; + let args = json!({ + "command": "echo shell command", + "timeout_ms": 1_000, + }); + let responses = vec![ + sse(vec![ + json!({"type": "response.created", "response": {"id": "resp-1"}}), + ev_function_call(call_id, "shell_command", &serde_json::to_string(&args)?), + ev_completed("resp-1"), + ]), + sse(vec![ + ev_assistant_message("msg-1", "shell_command done"), + ev_completed("resp-2"), + ]), + ]; + let mock = mount_sse_sequence(&server, responses).await; + + test.submit_turn_with_policy( + "run the shell_command script in the user's shell", + SandboxPolicy::DangerFullAccess, + ) + .await?; + + let req = mock + .last_request() + .expect("shell_command output request recorded"); + let output_item = req.function_call_output(call_id); + let output = output_item + .get("output") + .and_then(Value::as_str) + .expect("shell_command output string"); + + let expected_pattern = r"(?s)^Exit code: 0 +Wall time: [0-9]+(?:\.[0-9]+)? seconds +Output: +shell command +?$"; + assert_regex_match(expected_pattern, output); + + Ok(()) +} + #[tokio::test(flavor = "multi_thread", worker_threads = 2)] async fn local_shell_call_output_is_structured() -> Result<()> { skip_if_no_network!(Ok(())); From 7c8d3339806c1a228e1a179f9debb4dd511e20e6 Mon Sep 17 00:00:00 2001 From: jif-oai Date: Mon, 17 Nov 2025 17:10:53 +0100 Subject: [PATCH 7/8] feat: placeholder for image that can't be decoded to prevent 400 (#6773) --- codex-rs/protocol/src/models.rs | 19 +++++++++++++++++-- codex-rs/utils/image/src/error.rs | 13 +++++++++++++ 2 files changed, 30 insertions(+), 2 deletions(-) diff --git a/codex-rs/protocol/src/models.rs b/codex-rs/protocol/src/models.rs index f44d847099..755b59f535 100644 --- a/codex-rs/protocol/src/models.rs +++ b/codex-rs/protocol/src/models.rs @@ -158,6 +158,19 @@ fn local_image_error_placeholder( } } +fn invalid_image_error_placeholder( + path: &std::path::Path, + error: impl std::fmt::Display, +) -> ContentItem { + ContentItem::InputText { + text: format!( + "Image located at `{}` is invalid: {}", + path.display(), + error + ), + } +} + impl From for ResponseItem { fn from(item: ResponseInputItem) -> Self { match item { @@ -247,9 +260,10 @@ impl From> for ResponseInputItem { image_url: image.into_data_url(), }, Err(err) => { - tracing::warn!("Failed to resize image {}: {}", path.display(), err); if matches!(&err, ImageProcessingError::Read { .. }) { local_image_error_placeholder(&path, &err) + } else if err.is_invalid_image() { + invalid_image_error_placeholder(&path, &err) } else { match std::fs::read(&path) { Ok(bytes) => { @@ -365,6 +379,7 @@ impl Serialize for FunctionCallOutputPayload { where S: Serializer, { + tracing::error!("Payload: {:?}", self); if let Some(items) = &self.content_items { items.serialize(serializer) } else { @@ -452,7 +467,7 @@ fn convert_content_blocks_to_items( ) -> Option> { let mut saw_image = false; let mut items = Vec::with_capacity(blocks.len()); - + tracing::warn!("Blocks: {:?}", blocks); for block in blocks { match block { ContentBlock::TextContent(text) => { diff --git a/codex-rs/utils/image/src/error.rs b/codex-rs/utils/image/src/error.rs index ffd0a7850e..6bd055115d 100644 --- a/codex-rs/utils/image/src/error.rs +++ b/codex-rs/utils/image/src/error.rs @@ -1,3 +1,4 @@ +use image::ImageError; use image::ImageFormat; use std::path::PathBuf; use thiserror::Error; @@ -23,3 +24,15 @@ pub enum ImageProcessingError { source: image::ImageError, }, } + +impl ImageProcessingError { + pub fn is_invalid_image(&self) -> bool { + matches!( + self, + ImageProcessingError::Decode { + source: ImageError::Decoding(_), + .. + } + ) + } +} From 98a90a3bb2567e45cf1242b9dcf6959f873776a6 Mon Sep 17 00:00:00 2001 From: jif-oai Date: Mon, 17 Nov 2025 17:39:15 +0100 Subject: [PATCH 8/8] tmp: drop sccache for windows 2 (#6775) --- .github/workflows/rust-ci.yml | 37 ++++++++++++++++++++++++----------- 1 file changed, 26 insertions(+), 11 deletions(-) diff --git a/.github/workflows/rust-ci.yml b/.github/workflows/rust-ci.yml index 5d3103d7b2..0bd91ca53b 100644 --- a/.github/workflows/rust-ci.yml +++ b/.github/workflows/rust-ci.yml @@ -95,8 +95,8 @@ jobs: run: working-directory: codex-rs env: - # Speed up repeated builds across CI runs by caching compiled objects. - RUSTC_WRAPPER: sccache + # Speed up repeated builds across CI runs by caching compiled objects (non-Windows). + USE_SCCACHE: ${{ startsWith(matrix.runner, 'windows') && 'false' || 'true' }} CARGO_INCREMENTAL: "0" SCCACHE_CACHE_SIZE: 10G @@ -170,12 +170,14 @@ jobs: # Install and restore sccache cache - name: Install sccache + if: ${{ env.USE_SCCACHE == 'true' }} uses: taiki-e/install-action@44c6d64aa62cd779e873306675c7a58e86d6d532 # v2 with: tool: sccache version: 0.7.5 - name: Configure sccache backend + if: ${{ env.USE_SCCACHE == 'true' }} shell: bash run: | set -euo pipefail @@ -188,8 +190,13 @@ jobs: echo "Using sccache local disk + actions/cache fallback" fi + - name: Enable sccache wrapper + if: ${{ env.USE_SCCACHE == 'true' }} + shell: bash + run: echo "RUSTC_WRAPPER=sccache" >> "$GITHUB_ENV" + - name: Restore sccache cache (fallback) - if: ${{ env.SCCACHE_GHA_ENABLED != 'true' }} + if: ${{ env.USE_SCCACHE == 'true' && env.SCCACHE_GHA_ENABLED != 'true' }} id: cache_sccache_restore uses: actions/cache/restore@v4 with: @@ -274,7 +281,7 @@ jobs: key: cargo-home-${{ matrix.runner }}-${{ matrix.target }}-${{ matrix.profile }}-${{ hashFiles('**/Cargo.lock') }}-${{ hashFiles('codex-rs/rust-toolchain.toml') }} - name: Save sccache cache (fallback) - if: always() && !cancelled() && env.SCCACHE_GHA_ENABLED != 'true' + if: always() && !cancelled() && env.USE_SCCACHE == 'true' && env.SCCACHE_GHA_ENABLED != 'true' continue-on-error: true uses: actions/cache/save@v4 with: @@ -282,12 +289,12 @@ jobs: key: sccache-${{ matrix.runner }}-${{ matrix.target }}-${{ matrix.profile }}-${{ hashFiles('**/Cargo.lock') }}-${{ github.run_id }} - name: sccache stats - if: always() + if: always() && env.USE_SCCACHE == 'true' continue-on-error: true run: sccache --show-stats || true - name: sccache summary - if: always() + if: always() && env.USE_SCCACHE == 'true' shell: bash run: | { @@ -326,7 +333,8 @@ jobs: run: working-directory: codex-rs env: - RUSTC_WRAPPER: sccache + # Speed up repeated builds across CI runs by caching compiled objects (non-Windows). + USE_SCCACHE: ${{ startsWith(matrix.runner, 'windows') && 'false' || 'true' }} CARGO_INCREMENTAL: "0" SCCACHE_CACHE_SIZE: 10G @@ -370,12 +378,14 @@ jobs: cargo-home-${{ matrix.runner }}-${{ matrix.target }}-${{ matrix.profile }}- - name: Install sccache + if: ${{ env.USE_SCCACHE == 'true' }} uses: taiki-e/install-action@44c6d64aa62cd779e873306675c7a58e86d6d532 # v2 with: tool: sccache version: 0.7.5 - name: Configure sccache backend + if: ${{ env.USE_SCCACHE == 'true' }} shell: bash run: | set -euo pipefail @@ -388,8 +398,13 @@ jobs: echo "Using sccache local disk + actions/cache fallback" fi + - name: Enable sccache wrapper + if: ${{ env.USE_SCCACHE == 'true' }} + shell: bash + run: echo "RUSTC_WRAPPER=sccache" >> "$GITHUB_ENV" + - name: Restore sccache cache (fallback) - if: ${{ env.SCCACHE_GHA_ENABLED != 'true' }} + if: ${{ env.USE_SCCACHE == 'true' && env.SCCACHE_GHA_ENABLED != 'true' }} id: cache_sccache_restore uses: actions/cache/restore@v4 with: @@ -424,7 +439,7 @@ jobs: key: cargo-home-${{ matrix.runner }}-${{ matrix.target }}-${{ matrix.profile }}-${{ hashFiles('**/Cargo.lock') }}-${{ hashFiles('codex-rs/rust-toolchain.toml') }} - name: Save sccache cache (fallback) - if: always() && !cancelled() && env.SCCACHE_GHA_ENABLED != 'true' + if: always() && !cancelled() && env.USE_SCCACHE == 'true' && env.SCCACHE_GHA_ENABLED != 'true' continue-on-error: true uses: actions/cache/save@v4 with: @@ -432,12 +447,12 @@ jobs: key: sccache-${{ matrix.runner }}-${{ matrix.target }}-${{ matrix.profile }}-${{ hashFiles('**/Cargo.lock') }}-${{ github.run_id }} - name: sccache stats - if: always() + if: always() && env.USE_SCCACHE == 'true' continue-on-error: true run: sccache --show-stats || true - name: sccache summary - if: always() + if: always() && env.USE_SCCACHE == 'true' shell: bash run: | {