`gitDiffToRemote` silently omits untracked files with non-ASCII filenames (quotepath C-quoted paths used verbatim)
What issue are you seeing?
codex app-server's gitDiffToRemote silently omits every untracked file whose name contains a non-ASCII character (or ", \, control chars) from the returned diff. There is no error and no warning — the diff simply arrives incomplete, so any client that attaches the local diff (e.g. IDE-extension / cloud-task flows) works from code that is missing those files entirely.
With git's default core.quotepath=true, this affects every filename containing accented or CJK characters (héllo.txt, 笔记.md, …), which is an everyday case for non-English-speaking users.
Reproduced on codex-cli 0.147.0 (npm), macOS arm64, git 2.50.1. The relevant code is unchanged at HEAD 646f7c0a91b8e327d263335da68ae8ef212895ce; the behavior is platform-independent (it comes from git's quoting, not the OS).
What steps can reproduce the bug?
Setup (offline, no sign-in needed):
mkdir -p /tmp/codex-diff-repro && cd /tmp/codex-diff-repro
git init --bare origin.git
git clone origin.git work && cd work
git commit --allow-empty -m init
git push origin HEAD
printf 'unicode-named file\n' > 'héllo.txt'
printf 'ascii-named file\n' > normal.txt
End-to-end via the app-server:
(printf '{"method":"initialize","id":1,"params":{"clientInfo":{"name":"repro","title":"repro","version":"0.0.1"}}}\n'
sleep 2
printf '{"method":"initialized"}\n'
sleep 1
printf '{"method":"gitDiffToRemote","id":2,"params":{"cwd":"/tmp/codex-diff-repro/work"}}\n'
sleep 5) | codex app-server
Observed response (0.147.0) — the diff contains only normal.txt; héllo.txt is gone:
{"id":2,"result":{"sha":"…","diff":"diff --git a/normal.txt b/normal.txt\nnew file mode 100644\nindex 0000000..dae9dac\n--- /dev/null\n+++ b/normal.txt\n@@ -0,0 +1 @@\n+ascii-named file\n"}}
Root cause is visible with plain git, replaying what the code does per file:
$ git ls-files --others --exclude-standard # parsed line-by-line by info.rs
"h\303\251llo.txt" # C-quoted literal, quotes included
normal.txt
$ git diff --no-textconv --no-ext-diff --binary --no-index -- /dev/null '"h\303\251llo.txt"'
error: Could not access '"h\303\251llo.txt"' # exit code 1, empty stdout
What is the expected behavior?
Untracked files appear in the gitDiffToRemote diff regardless of what characters their names contain — or, failing that, an error is surfaced instead of silently returning an incomplete diff.
Additional information
Root-cause analysis (paths/lines at HEAD 646f7c0):
codex-rs/git-utils/src/info.rs:723-737—diff_against_shacollects untracked files withgit ls-files --others --exclude-standardand parses stdout with.lines(). Neither-znor acore.quotepathoverride is used (run_git_command_with_timeout_fromatinfo.rs:410-427only injectssafe.bareRepository,core.hooksPath, and fsmonitor config).- With git's default
core.quotepath=true, any filename byte > 0x7F — i.e. every non-ASCII name — plus",\, and control characters is emitted as a C-quoted string including the surrounding double quotes (e.g."h\303\251llo.txt"). info.rs:742-756— that quoted literal is passed verbatim as a path togit diff --no-textconv --no-ext-diff --binary --no-index -- /dev/null <name>. No file exists under the literal quoted name, so git printserror: Could not access …to stderr and exits 1 with empty stdout.info.rs:758-763— exit code 1 must be accepted here (--no-indexexits 1 when a diff exists), stdout is appended, and stderr is never inspected — so the failure is indistinguishable from "empty diff" and the file silently disappears.
Fix outline:
- Use
git ls-files -z --others --exclude-standardand split on NUL. This is robust for all names — including ones containing",\, or newlines, whichcore.quotepath=offalone would still quote. - Optionally harden step 4: treat a
--no-indexrun with empty stdout and non-empty stderr as a failure rather than appending nothing, so future errors in this path can't be silently swallowed.
The existing tests only cover ASCII untracked names (core/src/git_info_tests.rs), which is why this never tripped CI. Happy to provide more detail if useful.
2 Comments
Confirming that the suggested fix works. I implemented the
git ls-files -zapproach locally, preserved filenames as OS-native paths, and changed per-file Git failures so they return an error instead of silently dropping files.One Windows detail: this path should use
/dev/null, notNUL. Native Git for Windows accepts/dev/null, while MSYS2 Git requires it. UsingNULcaused the first version of the patch to fail under MSYS2 Git.I added a regression test with an untracked
héllo.txt. It failed before the change and now passes with both native Git for Windows and MSYS2 Git. The fullcodex-git-utilstest suite passes 34/34 with native Git.I have a focused local patch ready. If this approach matches what the maintainers want, I can submit it if invited, or the team can apply the same change internally.
Verified on
main(1f41cc5d92), and it's in two places, not one. The app-server path you diagnosed:git-utils/src/info.rs#L724-L760splitsls-files --others --exclude-standardon newlines and passes each string verbatim togit diff --no-index -- /dev/null <file>; a C-quoted name ("h\303\251llo.txt", literal quotes and octal escapes) names a nonexistent path,git diffexits ≠ 0/1, and the per-file status filter silently drops it (https://github.com/openai/codex/blob/1f41cc5d92/codex-rs/git-utils/src/info.rs#L740-L760). The TUI's/diffhas an independent copy of the same pattern (tui/src/get_git_diff.rs#L85-L120), so interactive diffs under-report the same files.The robust fix is NUL delimiting rather than fighting the quoting:
ls-files -z --others --exclude-standardemits raw bytes with no quotepath processing; split on\0and the accented/CJK/quote/backslash cases all disappear (also fixes names containing newlines, whichcore.quotepath=falsealone would not). Two call sites, same three-line change each — plus, per your silent-omission point, the per-file status filter should log/count the files it drops instead of flattening them away, so a future quoting regression is visible instead of shipping incomplete diffs to cloud tasks.