fix(skills): warn when blob GC is skipped over a malformed ledger line

gc_blobs() aborts the sweep on a json.JSONDecodeError (tools/skill_ledger.py:351-352)
but did so silently, while the sibling read-failure abort a few lines above logs
"ledger unreadable (...); blob GC skipped". `hermes curator ledger --compact` therefore
printed "0 blobs removed" as if the sweep had run and found nothing. Emit the same
warning family so the operator learns the ledger needs repair before blobs can be GC'd.

Test: `malformed-json` param on the kept invariant test — (0, 0), blobs intact, and
"blob GC skipped" in caplog. RED with the warning removed
(assert 'blob GC skipped' in ''), GREEN at head.
This commit is contained in:
kshitijk4poor
2026-09-22 12:35:39 +05:30
committed by kshitij
parent 1cb30951b1
commit 49aa7215e8
2 changed files with 4 additions and 1 deletions

View File

@@ -94,7 +94,7 @@ def test_gc_blobs_removes_only_unreferenced(ledger_home):
assert skill_ledger.gc_blobs() == (0, 0)
@pytest.mark.parametrize("failure", ["unreadable", "missing", "invalid-encoding"])
@pytest.mark.parametrize("failure", ["unreadable", "missing", "invalid-encoding", "malformed-json"])
def test_gc_keeps_rollback_blobs_when_the_ledger_cannot_be_read(ledger_home, monkeypatch, caplog, failure):
from tools import skill_ledger
@@ -119,6 +119,8 @@ def test_gc_keeps_rollback_blobs_when_the_ledger_cannot_be_read(ledger_home, mon
patch.setattr(Path, "read_text", read_text)
elif failure == "missing":
ledger.unlink()
elif failure == "malformed-json":
ledger.write_bytes(saved + b"{broken\n")
else:
ledger.write_bytes(b"\xff")
assert skill_ledger.gc_blobs() == (0, 0)

View File

@@ -349,6 +349,7 @@ def gc_blobs() -> Tuple[int, int]:
try:
row = json.loads(line)
except json.JSONDecodeError:
logger.warning("skill_ledger: malformed ledger line; blob GC skipped")
return 0, 0
for item in (row.get("before") or []) + (row.get("after") or []):
referenced.add(str(item.get("sha256", "")))