diff --git a/src/benchmark_radar/data_store.py b/src/benchmark_radar/data_store.py index f31b6017..bacb1e20 100644 --- a/src/benchmark_radar/data_store.py +++ b/src/benchmark_radar/data_store.py @@ -73,7 +73,7 @@ def _read_json(path: Path, *, label: str) -> dict[str, Any]: code="not_initialized", status=409, ) from error - except (OSError, json.JSONDecodeError) as error: + except (OSError, UnicodeDecodeError, json.JSONDecodeError) as error: raise DataSyncError( f"cannot read {label} at {path}: {type(error).__name__}: {error}", code="invalid_local_state", diff --git a/tests/test_data_sync.py b/tests/test_data_sync.py index c17a6bf7..dfc72347 100644 --- a/tests/test_data_sync.py +++ b/tests/test_data_sync.py @@ -546,3 +546,28 @@ def test_unsafe_archive_member_is_rejected(tmp_path: Path) -> None: with pytest.raises(DataSyncError, match="unsafe archive path"): store.initialize() assert not (tmp_path / "escaped.json").exists() + + +def test_invalid_state_encoding_has_the_local_state_error_contract(tmp_path): + # A damaged UTF-8 state file bypassed DataSyncError, so offline clients + # received a traceback instead of the same contract as malformed JSON. + original = b'{"schema_version": 1, "data_version": "\xff"}' + (tmp_path / "state.json").write_bytes(original) + with pytest.raises(DataSyncError) as captured: + DataStore(root=tmp_path).state() + assert captured.value.code == "invalid_local_state" + assert (tmp_path / "state.json").read_bytes() == original + + +@pytest.mark.parametrize("command", [["sync"], ["search", "agent"]]) +def test_corrupted_state_stays_structured_in_installed_cli(tmp_path, monkeypatch, capsys, command): + original = b"\xff" + (tmp_path / "state.json").write_bytes(original) + monkeypatch.setenv("BENCHMARK_RADAR_HOME", str(tmp_path)) + assert run_query_cli([*command, "--json"]) == 1 + output = capsys.readouterr() + payload = json.loads(output.err) + assert payload["error"]["code"] == "invalid_local_state" + assert output.out == "" + assert "Traceback" not in output.err + assert (tmp_path / "state.json").read_bytes() == original