fix: read and write config files as UTF-8 - #439
Open
LHMQ878 wants to merge 1 commit into
Open
Conversation
`open()` without an explicit `encoding` uses the platform's preferred
encoding, which on a stock Windows install is a legacy codepage (cp936 on
a Chinese system, cp1252 on a Western one) rather than UTF-8. Any config
file holding a non-ASCII byte — a Chinese comment, an accented name, an
em dash — then fails to load there while loading fine on Linux/macOS.
Observed on Windows 11 with a cp936 locale:
Config.create(config_file="trae_config.yaml")
UnicodeDecodeError: 'gbk' codec can't decode byte 0xad in position 4
`LegacyConfig` is worse than a crash: it catches the decode error, prints
a warning and falls back to defaults, so every setting the user wrote is
silently discarded (`max_steps: 42` becomes 20).
Four sites fixed:
- `utils/config.py:220` — YAML config, raised UnicodeDecodeError
- `utils/legacy_config.py:90` — JSON config, silently reverted to defaults
- `agent/trae_agent.py:167` — patch output; a diff containing any character
outside the codepage raised UnicodeEncodeError, and one containing
non-ASCII text was written in codepage bytes that UTF-8 readers reject
- `tools/ckg/ckg_database.py:161,184` — CKG storage info, keyed by absolute
codebase paths, so a non-ASCII path breaks index reuse
The patch write also passes `newline=""`. Universal newline translation
rewrites the diff's LF endings to CRLF on Windows, and `git apply` rejects
a patch whose lines end in CRLF.
`json_edit_tool.py` and `trajectory_recorder.py` already pass
`encoding="utf-8"`; this brings the remaining file I/O in line with them.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Description
open()without an explicitencodinguses the platform's preferred encoding, which on a stock Windows install is a legacy codepage — cp936 on a Chinese system, cp1252 on a Western one — not UTF-8. Every config filetrae-agentreads is UTF-8, so any config holding a single non-ASCII byte (a Chinese comment, an accented name, an em dash) fails to load on those machines while loading fine on Linux/macOS.Observed on Windows 11 with a cp936 locale, using a
trae_config.yamlwhose only non-ASCII content is a comment:LegacyConfigis worse than a crash. It catches the decode error, prints a warning, and falls back to defaults — so every setting the user wrote is silently discarded:The user gets a working agent running on settings they never chose.
More Information
Four sites, all
open()calls missingencoding=:trae_agent/utils/config.pyUnicodeDecodeError, agent won't starttrae_agent/utils/legacy_config.pytrae_agent/agent/trae_agent.pytrae_agent/tools/ckg/ckg_database.pyThe patch-output site fails in two distinct ways, both verified:
That output also shows a second defect:
\r\n. Universal newline translation rewrites the diff's LF endings to CRLF on Windows, andgit applyrejects a patch whose lines end in CRLF. The fix passesnewline=""so the diff bytes reach disk unmodified. A comment records why.ckg_database.pystores a map keyed bycodebase_path.absolute().as_posix(). A codebase under a path containing non-ASCII characters — a Chinese username, an accented directory — breaks index reuse and rebuilds the CKG every run.This is a consistency fix as much as a bug fix:
tools/json_edit_tool.py(163, 178),tools/json_edit_tool_cli.py(79, 93) andutils/trajectory_recorder.py(226) already passencoding="utf-8". The four sites above are the remaining text-modeopen()calls intrae_agent/that don't.docker_manager.py:73opens in binary mode and is correctly left alone.Validation
New test file
tests/utils/test_config_encoding.py, 3 tests, matching theunitteststyle of the neighbouringtest_config.py.Reproducing this in CI needs care. Monkeypatching
locale.getpreferredencodingdoes not work — CPython reads the locale encoding at the C level, soopen()ignores the patched function (I verified this: the patched call still raised'gbk' codec, not'ascii' codec). Instead alegacy_default_encodingcontext manager replacesopenwith a shim that supplies a legacy codec in exactly the position CPython would supply the locale's — that is, only when the caller passed noencoding:Code that names its encoding is untouched by the shim. So these tests fail on a machine of any locale when the
encoding=is missing, and pass on a machine of any locale when it is present — they will catch a regression on the project's UTF-8 Linux runners, not only on a Chinese Windows box.The
LegacyConfigtest asserts on the resulting values, not on an exception, because that path swallows the error — assertingmax_steps == 42is the only way to catch a silent revert.Control experiment — implementation reverted to the unpatched
open()calls, new tests kept:The one that stays green is
test_ascii_yaml_config_still_loads_under_legacy_locale— the ASCII case the old code got right and the fix must not regress.With the fix: 3 passed. Together with the existing config tests:
tests/utils/test_config.py tests/utils/test_config_encoding.py→ 14 passed.No regressions. Whole suite, excluding the three modules that need a live network endpoint (
test_ollama_client_utils,test_openrouter_client_utils,test_google_client):mainIdentical 5 failures on both —
tests/tools/test_bash_tool.py(3) andtests/tools/test_json_edit_tool.py(2), all pre-existing and unrelated (pexpectis not available on Windows). The delta is exactly the 3 new tests.ruff check→ All checks passed.ruff format --check→ 49 files already formatted.mypyon the new test file → Success, no issues.Behaviour is unchanged for every config file that loaded correctly before, so no working setup shifts.
Linked Issues
None — I did not find an existing issue for this (searched
encoding,UnicodeDecodeError,utf-8,gbk/cp936/codepage: 0 results). Happy to open one if you'd prefer the issue-first flow.