fix(tools): keep invalid UTF-8 boundaries binary - #76934
fix(tools): keep invalid UTF-8 boundaries binary#76934Christopher-Schulze wants to merge 3 commits into
Conversation
teknium1
left a comment
There was a problem hiding this comment.
Thanks for addressing the confirmed boundary regression; current main still samples with head -c 1000 in both reads (tools/file_operations.py:1168, :1286) and rejects its U+FFFD output at :896.
Problems
- Blocking:
tools/file_operations.py:907removes a lone trailing U+FFFD from the 1004-byte retry without knowing whether it is a valid UTF-8 boundary artifact. For 999 ASCII bytes, a validçspanning bytes 1000-1001, two ASCII bytes, then an invalid0xffat byte 1004 followed by content, the initial sample triggers the retry and the extended output has exactly one trailing U+FFFD. This line removes the invalid-byte signal, so_is_likely_binary()accepts malformed content instead of preserving its existing fail-closed behavior.
Suggested changes
- Retain the fail-closed signal unless the implementation can distinguish raw valid UTF-8 continuation bytes from an invalid byte at the extended boundary.
- Add this extended-boundary invalid-byte case for both
read_fileandread_file_raw; the current adversarial case attests/tools/test_file_operations.py:717only covers an invalid byte in the initial window.
Automated hermes-sweeper review.
a221374 to
143bd8c
Compare
Independent verification — Windows native (WinGet rg + Git Bash)Verified the current diff on a native Windows host (Git Bash/MSYS terminal backend, Result: 9/9 pass — all 8 tests in This covers the exact matrix the #76886 triage asked for: the original Turkish/CJK boundary repro ('ç' starting at byte 1000), repeated valid boundary splits, invalid bytes at byte 1000 and at the extended boundary, EOF-truncated sequences, and both Before/after on the original repro (same file, same content, one byte of offset):
One portability note (non-blocking): the new tests use This PR is technically ready: MERGEABLE + CLEAN, CI green, and now independently verified on the platform where the bug reproduces. |
143bd8c to
05fe260
Compare
|
Maintenance update: rebased onto current |
05fe260 to
cf64b6f
Compare
Maintenance updateRebased and published the fail-closed UTF-8 boundary fix on current The extended-boundary retry now removes a replacement character only after a bounded raw-byte probe proves an incomplete valid sequence; genuine invalid bytes remain binary in both
GitHub CI is running on this head. |
|
Confirming this affects Korean UTF-8 Markdown in real use. In Hermes Desktop, long Korean Obsidian This blocks both reading and editing otherwise valid notes, so the workaround is not practical for a Korean knowledge-base workflow. #76934 looks like the safest consolidation candidate because it preserves rejection of genuinely invalid UTF-8 while covering both read paths. I can test the final merged build on Korean Markdown files if useful. |
|
Hi — just closed our own competing duplicate (#81711) in favor of this PR per the contributing guide. Your raw-byte probe approach is strictly stronger than the trailing-char exemption; thanks for the fail-closed design. One gap worth closing: this PR references #76886 but not #80308 — the same bug reported by a CJK user (valid UTF-8 CJK files misflagged as binary on Windows/git-bash). This fix covers that report too; adding FWIW, our closed PR included a CJK-specific regression test ( |
cf64b6f to
9179af0
Compare
|
Maintenance update: added a dense-CJK boundary regression covering both read_file and read_file_raw, and the PR description now records coverage of #80308. The fail-closed invalid-byte matrix remains intact. Rebased onto current main; 56/56 focused tests and the canonical checker pass on head 9179af0. #81711 was closed in favor of this PR; the remaining linked lanes were reviewed during the authorized existing-PR update. |
|
Closing — superseded by #81961, which fixed this bug class (1000-byte sample cutting a multibyte char → binary false positive) at the byte layer across read_file, patch, and search_files, with regression tests for truncated-CJK/BOM/UTF-16/NUL cases. ~24 PRs raced on this one; earliest diagnosis credit to @webtecnica (#76924), merged implementation from @ayushnangia (#80440). Thanks for jumping on it. |
What does this PR do?
read_fileandread_file_rawsample the first 1000 bytes through a terminal stream decoded witherrors="replace". When that boundary splits a valid UTF-8 character, the lossy sample contains U+FFFD and the file is incorrectly rejected as binary. This is reproducible with ordinary UTF-8 notes containing Turkish, CJK, or other multibyte text.The shared binary-detection sampler now performs a small boundary-only extension when the first sample ends with U+FFFD. It accepts valid UTF-8 at the boundary only after a bounded raw-byte probe proves that the replacement is an incomplete sequence; genuine undecodable bytes and truncated content remain fail-closed.
Two contemporaneous direct fixes remain open as #76924 and #76925; the later duplicate #81711 was closed in favor of this PR. This branch is materially stronger for the shared backend surface: it keeps the existing shell abstraction, performs one bounded optional
odprobe only for the boundary signal, fails closed when that probe is unavailable, and tests the adversarial case where a real invalid byte sits exactly at the extended boundary. That guard is absent from #76925's unconditional trailing-character exemption, while #76924 pays for repeated raw-byte shell probes instead of this single bounded validation.Related Issue
Fixes #76886; fixes #80308
Type of Change
Changes Made
tools/file_operations.py: centralize the 1000-byte binary sample, retry only when the decoded sample ends at a possible UTF-8 boundary, strictly validate the bounded raw bytes before removing a replacement, and retain the original boundary signal on every unproven path.tests/tools/test_file_operations.py: exercise paginated and raw reads with valid multibyte characters at the initial and extended boundaries, add a dense CJK boundary regression for both public paths, and verify invalid bytes at both boundaries remain blocked.How to Test
4983c576b), a realerrors="replace"subprocess probe classified 999 ASCII bytes followed by a valid UTF-8çasis_binary=True.scripts/run_tests.sh tests/tools/test_file_operations.py -q-> 56 passed..venv/bin/ruff check tools/file_operations.py tests/tools/test_file_operations.py-> passed.9179af050is rebased onto currentorigin/main3d7dda4cf; the canonical changed-file gate passes with 56 tests, Ruff,uv lock --check, andgit diff --check. Existing repository-wide ty diagnostics remain advisory only.0xffbytes at the initial and extended sample boundaries remained binary for both public read paths.Checklist
Code
fix(tools): ...).Documentation & Housekeeping
odis unavailable or cannot prove the boundary.