Skip to content

Commit 3894a87

Browse files
ptr727claude
andcommitted
Catch the Decode Error Too, at Both Reads That Parse a File
`report` and `registration_problems` each caught JSONDecodeError and OSError, and a file holding bytes no decoder accepts raises UnicodeDecodeError before the JSON parser is reached. A partially written stamp therefore crashed the read-only report with a traceback, which is what those handlers exist to prevent. Both catch ValueError now, which covers JSONDecodeError and UnicodeDecodeError alike, rather than naming the subclass and missing its sibling. Two cases added, one per call site, both failing against the previous code. The settings one is included because the finding named only the stamp and the same shape was two hundred lines above it. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
1 parent 6894146 commit 3894a87

2 files changed

Lines changed: 23 additions & 2 deletions

File tree

‎host-setup/agent-safety/install.py‎

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -294,7 +294,9 @@ def registration_problems(claude_home):
294294
return ["settings.json is missing, so the hook is not registered"]
295295
try:
296296
data=json.loads(settings.read_text(encoding="utf-8") or"{}")
297-
except (json.JSONDecodeError, OSError) ase:
297+
# ValueError rather than JSONDecodeError, since it also covers UnicodeDecodeError.
298+
# A partially written or non-UTF-8 file raises that before the JSON parser is ever reached.
299+
except (ValueError, OSError) ase:
298300
return [f"settings.json cannot be read ({e})"]
299301
ifnotisinstance(data, dict):
300302
return ["settings.json does not hold an object at its root"]
@@ -353,7 +355,9 @@ def report(claude_home):
353355
return2
354356
try:
355357
stamp=json.loads(path.read_text(encoding="utf-8"))
356-
except (json.JSONDecodeError, OSError) ase:
358+
# ValueError rather than JSONDecodeError, since it also covers UnicodeDecodeError.
359+
# A partially written or non-UTF-8 file raises that before the JSON parser is ever reached.
360+
except (ValueError, OSError) ase:
357361
sys.stderr.write(f"Stamp at {path} is unreadable ({e}). Re-run the installer to rewrite it.\n")
358362
return2
359363
# Valid JSON is not a usable stamp: a hand edit or an older format parses and then breaks the read.

‎host-setup/agent-safety/test_install.py‎

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -211,6 +211,23 @@ def test_a_stamp_missing_required_keys_gives_a_verdict_rather_than_a_traceback(s
211211
self.assertIn("missing", r.stderr)
212212
self.assertNotIn("Traceback", r.stderr)
213213

214+
deftest_a_stamp_holding_invalid_utf8_gives_a_verdict_rather_than_a_traceback(self):
215+
"""A partial write leaves bytes no decoder accepts, which raises before JSON is reached."""
216+
self.install()
217+
self.stamp.write_bytes(b'{"host": "\xff\xfe not utf-8"}')
218+
r=run(self.home, "--report")
219+
self.assertEqual(r.returncode, 2, r.stdout+r.stderr)
220+
self.assertIn("unreadable", r.stderr)
221+
self.assertNotIn("Traceback", r.stderr)
222+
223+
deftest_settings_holding_invalid_utf8_reports_stale_rather_than_a_traceback(self):
224+
"""The registration read has the same shape and needed the same widening."""
225+
self.install()
226+
(self.home/"settings.json").write_bytes(b'{"hooks": "\xff\xfe"}')
227+
r=run(self.home, "--report")
228+
self.assertEqual(r.returncode, 1, r.stdout+r.stderr)
229+
self.assertNotIn("Traceback", r.stderr)
230+
214231
deftest_a_stamp_holding_a_non_object_gives_a_verdict_rather_than_a_traceback(self):
215232
self.install()
216233
self.stamp.write_text("[]\n", encoding="utf-8")

0 commit comments

Comments
 (0)