Summary
A malformed JSONL file can terminate recomputation or the watcher. Invalid UTF-8 escapes load() as UnicodeDecodeError, and valid JSON scalar/array lines are accepted even though all downstream code expects dictionaries.
Tested against main at fb265549480ff0d3ad8b14b28007370539b70cf3.
Reproduction
from pathlib import Path
import tempfile, meter
root = Path(tempfile.mkdtemp())
bad_utf8 = root / "bad-utf8.jsonl"
bad_utf8.write_bytes(b'{"type":"user"}\n\xff\n')
meter.load(str(bad_utf8)) # UnicodeDecodeError
non_objects = root / "non-objects.jsonl"
non_objects.write_text('[]\n42\n{"type":"user"}\n', encoding="utf-8")
rows = meter.load(str(non_objects)) # returns list, int, dict
# recompute() later calls .get() and raises AttributeError on the list/int
Truncated JSON and ordinary invalid JSON are skipped successfully; these two cases are not.
Root cause
meter.py:293-307 reads the entire file as strict UTF-8, only catches FileNotFoundError around I/O, and appends any value accepted by json.loads() without checking its type.
Suggested fix
- Stream the file line by line rather than reading the full log into memory.
- Handle decode failures per line (for example, open with
errors="replace" and skip lines that no longer parse) and catch relevant OSError/UnicodeError failures.
- Append only JSON objects:
if isinstance(value, dict): ....
- Record a bounded warning/counter for skipped rows so corruption is diagnosable without flooding logs.
- Preserve tolerance for a partially written final line.
Regression tests
- Valid row, invalid UTF-8, valid row: both valid dictionaries survive.
- Valid JSON array/string/number/null: all are skipped.
- Truncated final line and arbitrary garbage: no exception.
- Permission/read
OSError: watcher remains alive and reports degraded state.
Summary
A malformed JSONL file can terminate recomputation or the watcher. Invalid UTF-8 escapes
load()asUnicodeDecodeError, and valid JSON scalar/array lines are accepted even though all downstream code expects dictionaries.Tested against
mainatfb265549480ff0d3ad8b14b28007370539b70cf3.Reproduction
Truncated JSON and ordinary invalid JSON are skipped successfully; these two cases are not.
Root cause
meter.py:293-307reads the entire file as strict UTF-8, only catchesFileNotFoundErroraround I/O, and appends any value accepted byjson.loads()without checking its type.Suggested fix
errors="replace"and skip lines that no longer parse) and catch relevantOSError/UnicodeErrorfailures.if isinstance(value, dict): ....Regression tests
OSError: watcher remains alive and reports degraded state.