fix(cache): treat a non-UTF-8 cache file as a miss, not a crash - #161
Open
MohammedAlkindi wants to merge 1 commit into
Open
fix(cache): treat a non-UTF-8 cache file as a miss, not a crash#161MohammedAlkindi wants to merge 1 commit into
MohammedAlkindi wants to merge 1 commit into
Conversation
load_cache() and load_benchmark_cache() promise a cache miss on a bad
file: the docstring says "Returns None if expired or missing" and the
handler logs "Cache corrupted" and returns None. They catch
(json.JSONDecodeError, KeyError), but read_text(encoding="utf-8")
raises UnicodeDecodeError, which is a ValueError and not a
JSONDecodeError, so it escapes and reaches the CLI uncaught.
cli.py calls both loaders bare, e.g.
cached_data = None if refresh else load_cache()
so the user gets a traceback instead of a silent cache refresh.
This is reachable on the upgrade path rather than in theory. Before
07140d5 the cache was written with ensure_ascii=False through the
locale codepage, so on a Windows machine a cached model id containing
non-ASCII is sitting on disk right now as cp1252 bytes. Reading that
file with the current code raises
UnicodeDecodeError: 'utf-8' codec can't decode byte 0xe9 in
position 81: invalid continuation byte
A truncated write from a killed process produces the same class of
failure without any version history.
Adding UnicodeDecodeError to both handlers restores the documented
behaviour. Regression tests write a real cp1252 cache file, since the
existing fakes return str and cannot express this.
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.
Closes #160
Symptom
A cache file that is not valid UTF-8 exits whichllm with a traceback instead of refreshing the cache:
Both loaders promise the opposite.
load_cache()is documented as "Returns None if expired or missing", and both already carry a handler that logsCache corruptedand returnsNone.Cause
cache.pyandbenchmark_cache.pyread withread_text(encoding="utf-8")and then catch:UnicodeDecodeErroris aValueError, but it is not ajson.JSONDecodeError, so it is not caught.cli.pycalls both loaders bare (lines 623, 782, 868), so it propagates to the user.This is an upgrade path, not a hypothetical. Before 07140d5 the cache was written with
ensure_ascii=Falsethrough the locale codepage, so on Windows a cached model id containing non-ASCII is on disk as cp1252 bytes today. Upgrading to ≥0.5.12 fixes the writer but leaves that file, and the first run afterwards reads it back. A truncated write reaches the same place with no version history involved.Change
UnicodeDecodeErroradded to both handlers — two lines. An undecodable cache now degrades to a miss like every other corruption.Testing
Windows 11, Python 3.13,
locale.getpreferredencoding(False) == 'cp1252'.Two regression tests in the existing
tests/test_cache_encoding.py. They write a real cp1252 cache file, because the fakes already in that file returnstrand so cannot express a decode error. Reverting only the two source lines:With the change:
Full suite, same machine:
The 24 are pre-existing on a clean
ea32ed2checkout and live intest_gpu_simulator.pyandtest_nvidia_detection.py, which this PR does not touch. I diffed the twoFAILEDlists and they are identical, so no new failure is introduced — but I have not investigated why those 24 fail here, and CI is the authority on that.Deliberately unchanged
07140d5already pinnedencoding="utf-8"on both, so nothing new lands undecodable; this only stops the reader dying on files written before that._ReadableCacheFile/_WritableCacheFile. They are right for asserting which encoding a caller passes, and the new tests need real bytes instead, so both styles now sit side by side rather than one replacing the other.exceptclauses stay narrow. AddingOSErroror a bareexceptwould also swallow a genuinely unreadable cache directory, which is a different failure and should still surface.Limitations
ubuntu-latestonly, and the reproduction is Windows-specific because it depends on a cp1252 default encoding. The regression tests themselves are platform-independent: they write cp1252 bytes explicitly rather than relying on the ambient locale, so they exercise the same path on Linux CI.07140d5writer, not from a report.