Fixes #1679
Nine readers have the same shape: they read a UTF-8 file, and their handler lists (OSError, json.JSONDecodeError) while the behaviour below the except is "absent/empty". UnicodeDecodeError is a ValueError, so it matches neither slot and the exception leaves the call - which for the files below runs at startup (identity, subscription auth, viewer auth) or during artifact assembly (agent graph).
Grouped by what the caller sees today:
| reader | file | today | expected |
|---|---|---|---|
hydrate_todos_from_disk | todos.json | run fails to start | store starts empty |
hydrate_notes_from_disk | notes.json | run fails to start | store starts empty |
hydrate_threat_models_from_disk | threat_models.json | run fails to start | store starts empty |
hydrate_coverage_from_disk | coverage.json | run fails to start | store starts empty |
load_user_mcp_configs | mcp.json | run fails to start | no connections |
codex._read_store | subscription-auth.json | auth read raises | {} |
read_or_create_identity | identity.json | run fails to start | a fresh identity |
viewer_auth.read_auth | auth.json | viewer raises | signed out |
coverage.read_agent_graph | agents.json | artifact assembly raises | {} |
Each handler now lists UnicodeDecodeError, so the comment the code already promised ("unreadable; starting empty") is true for an undecodable file too.
Validation: pytest tests/test_undecodable_state_files.py -q -> 10 passed; with the source changes stashed the same file gives 5 failed then 4 failed (one per group), so every case is load-bearing. pytest tests/test_undecodable_state_files.py tests/test_inputs.py -q -> 63 passed. ruff check + format clean.
Found with a small AST scan for (OSError, json.JSONDecodeError) around a read_text(encoding="utf-8"): the remaining hits are write paths, or reads whose callers already fail loudly with a message, and those are left alone.
Activity
6Small status note for whoever picks this up, since the PR is one change repeated nine times and that can look bigger than it is:
- The bug in one line: a reader whose handler lists
(OSError, json.JSONDecodeError)never catchesUnicodeDecodeError, because that is aValueError. The code below each handler already means "unreadable -> start empty", so the intent exists and only the catch list was short. - Why it happens without tampering: these files are written UTF-8 with
ensure_ascii=False; a write interrupted mid-character leaves a file that is readable but not decodable. Foridentity.json,subscription-auth.jsonandauth.jsonthat happens at startup. - Shape of the diff: nine one-line handler changes plus one new test file (
tests/test_undecodable_state_files.py, 10 cases). Each case fails when its own handler change is reverted, so none of them is decorative. - What I deliberately did not touch: write paths, and reads whose callers already fail loudly with a message (for example
read_run_record, which raises aRuntimeErrornaming the path) - changing those would hide real errors instead of reporting them.
Happy to split it if you would rather review the tool stores and the startup files separately; say the word and I will re-cut it into two PRs.
Verification note, because the checks list can be misleading here: the repository's CI workflow (lint + pytest + tui) is in action_required for this PR, which means GitHub is waiting for a maintainer to approve the run for a fork. So the only green check on the PR is the third-party supply-chain scan, and no test has actually executed on CI yet.
What I ran locally on this branch instead:
pytest tests/test_undecodable_state_files.py -q-> 10 passedpytest tests/test_undecodable_state_files.py tests/test_inputs.py -q-> 63 passed- with each handler change reverted, the matching case fails (checked one group at a time: 5 failed / 1 passed, then 4 failed / 6 passed)
ruff checkandruff format --checkclean on the touched files
If you approve the workflow run, pytest should be the one that matters; mypy strix/ covers these files too and I could not run it here because the environment has no uv.
[Medium impact] The change is limited to five JSON-loading paths used during scan setup.
The PR appears safe to merge, with a non-blocking improvement needed in the healthy MCP test.
Findings
Fix with agent prompt
Summary
The PR catches
UnicodeDecodeErrorin four state stores and the user's MCP configuration loader.Reviews (1) · Last reviewed commit: "fix(tools): survive an undecodable state..." · Reviewed by Greptile