fix(read_file): stop misjudging UTF-8 CJK files as binary - #80188
fix(read_file): stop misjudging UTF-8 CJK files as binary#80188shihuaiya wants to merge 1 commit into
Conversation
read_file/read_file_raw sampled the file head with 'head -c 1000', which truncates at an arbitrary byte boundary. For UTF-8 files whose byte-1000 boundary lands inside a multi-byte char (CJK text, emoji), the terminal env decodes stdout with errors="replace", turning the dangling tail into U+FFFD — and _is_likely_binary's U+FFFD guard then misclassified a perfectly valid text file as binary. Replace the byte-truncating head sample with _sample_text_head(): a python -c snippet that reads the first 1000 bytes and decodes them as UTF-8, backing off up to 3 bytes on a truncated sequence so the sample always ends on a character boundary. Genuinely non-UTF-8 files (GBK etc.) still fail decode and yield U+FFFD, so the existing binary protection is preserved. Sampling failures err toward binary (safe). Windows details: paths are normalized to forward slashes and the fallback marker uses chr(0xFFFD) because _escape_shell_arg runs the snippet through _bash_safe_path, which rewrites backslashes (breaking repr'd paths and the '\ufffd' escape). Verification: character-boundary scan across 0..1001 byte prefixes, CJK/GBK/ASCII/empty/binary fixtures, read_file + read_file_raw.
|
Triage note: this appears to duplicate three other PRs that fix the same issue — UTF-8 files whose 1000-byte read_file sample lands mid-multibyte-char (trailing U+FFFD) get misclassified as binary. Same files (tools/file_operations.py + tests), same fix. The cluster: #80186, #80250, #80261. Recommend picking one to champion and closing the rest as duplicates. |
|
I reviewed all 5 open PRs for this issue (#76934, #79534, #79641, #80188, #80957) and wanted to share a cross-comparison, since they all target the same root cause. Root cause (all agree): The 5 approaches:
My assessment: #80188 is the strongest — it addresses the root cause at the sampling layer, is cross-platform aware, and preserves the existing binary guard. Two suggestions for it:
Happy to be corrected on any of this — just sharing the cross-comparison to help the merge decision. — Airi (IRIS), Hermes Agent |
|
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. |
read_file/read_file_raw sampled the file head with 'head -c 1000', which truncates at an arbitrary byte boundary. For UTF-8 files whose byte-1000 boundary lands inside a multi-byte char (CJK text, emoji), the terminal env decodes stdout with errors="replace", turning the dangling tail into U+FFFD — and _is_likely_binary's U+FFFD guard then misclassified a perfectly valid text file as binary.
Replace the byte-truncating head sample with _sample_text_head(): a python -c snippet that reads the first 1000 bytes and decodes them as UTF-8, backing off up to 3 bytes on a truncated sequence so the sample always ends on a character boundary. Genuinely non-UTF-8 files (GBK etc.) still fail decode and yield U+FFFD, so the existing binary protection is preserved. Sampling failures err toward binary (safe).
Windows details: paths are normalized to forward slashes and the fallback marker uses chr(0xFFFD) because _escape_shell_arg runs the snippet through _bash_safe_path, which rewrites backslashes (breaking repr'd paths and the '\ufffd' escape).
Verification: character-boundary scan across 0..1001 byte prefixes, CJK/GBK/ASCII/empty/binary fixtures, read_file + read_file_raw.
What does this PR do?
Related Issue
Fixes #
Type of Change
Changes Made
How to Test
Checklist
Code
fix(scope):,feat(scope):, etc.)pytest tests/ -qand all tests passDocumentation & Housekeeping
docs/, docstrings) — or N/Acli-config.yaml.exampleif I added/changed config keys — or N/ACONTRIBUTING.mdorAGENTS.mdif I changed architecture or workflows — or N/AFor New Skills
hermes --toolsets skills -q "Use the X skill to do Y"Screenshots / Logs