Bug: UTF-8 char-boundary panic in `summarize_patch_for_logging` on non-ASCII patches

Open 💬 1 comment Opened Jul 25, 2026 by afjal1

What version of Codex CLI is running?

Reproduced with codex-cli 0.145.0. The same implementation is still present in main at e4fb5311d7468839def62eabda4b268f4a54cf11.

What platform is your computer?

Darwin 26.5.2 arm64 arm (macOS).

What issue are you seeing?

summarize_patch_for_logging panics when a patch contains non-ASCII characters (e.g., file paths with UTF-8 characters like Chinese, Japanese, accented Latin, emoji) and the truncation point falls in the middle of a multi-byte character.

Root cause: Line 901 uses a byte-level slice:

let head_trunc = if head.len() > 800 {
    format!("{}…", &head[..800])  // panics if byte 800 is not a char boundary
} else {
    head
};

&head[..800] is a byte-level index. If the 800th byte falls in the middle of a multi-byte UTF-8 character (e.g., a 3-byte CJK character, a 4-byte emoji), Rust panics with:

byte index 800 is not a char boundary; it is inside '某' (bytes 798..801)

This is a logging function (summarize_patch_for_logging), so it won't lose user data, but it will abort the logging call and could unwind a ? chain upstream depending on the call site.

What steps can reproduce the bug?

  1. Create a file with a non-ASCII name or content that exceeds 800 bytes when the first 20 lines are joined
  2. Use apply_patch to modify that file
  3. The logging function panics when truncating

Alternatively, construct a patch string where bytes 798-800 contain a multi-byte character and call summarize_patch_for_logging directly.

What is the expected behavior?

The truncation should respect UTF-8 character boundaries. The function should truncate at the last complete character before byte 800.

Suggested fix

Replace &head[..800] with a char-boundary-safe truncation. Two options:

Option A (using floor_char_boundary, stable since Rust 1.73):

let head_trunc = if head.len() > 800 {
    let boundary = head.floor_char_boundary(800);
    format!("{}…", &head[..boundary])
} else {
    head
};

Option B (using char_indices, already used elsewhere in the codebase):

let head_trunc = if head.len() > 800 {
    let boundary = head.char_indices()
        .take_while(|(i, _)| *i <= 800)
        .last()
        .map(|(i, _)| i)
        .unwrap_or(800);
    format!("{}…", &head[..boundary])
} else {
    head
};

The Rust toolchain is pinned to 1.95.0, so floor_char_boundary is available and is the cleanest option.

Related

Similar class of bug to #34282 (rollout trace reducer panics on non-ASCII JSON truncation). This suggests a pattern of byte-level truncation across the codebase that should be audited for UTF-8 safety.

Scope

Single-line fix in one file (cloud-tasks-client/src/http.rs:901). No behavioral change — only the truncation boundary is made UTF-8-safe.

View original on GitHub ↗

1 Comment

hogeheer499-commits · 1 month ago

I reproduced this independently on Linux against current main (20dafe201d91). With 798 ASCII bytes followed by , byte 800 is not a character boundary and summarize_patch_for_logging panics as reported.

While checking the nearby truncation paths, I found two more byte slices with the same UTF-8 failure mode:

  • cloud-tasks-client/src/http.rs:874-878: tail slices at s.len() - max, which can land inside a multibyte character.
  • cloud-tasks/src/lib.rs:2123: &raw[..320] has the same prefix-truncation risk.

The workspace already has codex_utils_string::take_bytes_at_char_boundary for safe prefix truncation. If the team would like these handled together or as separate scoped fixes, I would be happy to prepare the invited PR and regression coverage.