#1680

fix: treat an undecodable state file as absent, not as a fatal error

Openlxcxjxhxwants to merge intofrom

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:

readerfiletodayexpected
hydrate_todos_from_disktodos.jsonrun fails to startstore starts empty
hydrate_notes_from_disknotes.jsonrun fails to startstore starts empty
hydrate_threat_models_from_diskthreat_models.jsonrun fails to startstore starts empty
hydrate_coverage_from_diskcoverage.jsonrun fails to startstore starts empty
load_user_mcp_configsmcp.jsonrun fails to startno connections
codex._read_storesubscription-auth.jsonauth read raises{}
read_or_create_identityidentity.jsonrun fails to starta fresh identity
viewer_auth.read_authauth.jsonviewer raisessigned out
coverage.read_agent_graphagents.jsonartifact 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

6
greptile-apps[bot]1d ago

Confidence Score: 4/5

[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

  1.  Healthy loading is not tested ▶
Fix with agent prompt
### Issue 1
tests/test_undecodable_state_files.py:61-63
This test writes an object, but `load_user_mcp_configs` requires a list of server entries. It tests rejection rather than successful loading, so it would pass even if the loader always returned `[]`. Write a list containing a valid server and assert that the server is returned.

```suggestion
    path.write_text(
        json.dumps([{"name": "example", "url": "https://example.com/mcp"}]), encoding="utf-8"
    )

    configs = mcp_loader.load_user_mcp_configs(path)
    assert len(configs) == 1
    assert configs[0].name == "example"
    assert configs[0].url == "https://example.com/mcp"
```

---

For each issue above, determine whether it is valid and should be fixed. If so, fix it directly.

Summary

The PR catches UnicodeDecodeError in four state stores and the user's MCP configuration loader.

  • Scan setup and MCP loading continue when their JSON files cannot be decoded.

Reviews (1) · Last reviewed commit: "fix(tools): survive an undecodable state..." · Reviewed by Greptile

greptile-apps[bot]reviewed1d ago
lxcxjxhx1d ago

Small 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 catches UnicodeDecodeError, because that is a ValueError. 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. For identity.json, subscription-auth.json and auth.json that 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 a RuntimeError naming 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.

lxcxjxhx1d ago

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 passed
  • pytest 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 check and ruff format --check clean 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.