Files
hermes-agent/tools
kshitijk4poor c86607124f fix(skill_ledger): hold .locks/ledger.lock across append + sweep and the CLI compact/GC path
`_maintain_size()` now runs inside every `append_entry`, and `compact_ledger`/
`_trim_oldest` do read-bytes -> `os.replace` over the ledger with no cross-writer
lock, so a concurrent appender's O_APPEND write landing on the replaced inode was
silently lost — and `gc_blobs` then deleted that row's blobs. `_ledger_lock()`
(same `skill_file_lock` idiom as `_skill_mutation_lock`; fcntl/msvcrt, re-entrant,
no-op where neither exists) is held across the append AND the sweep, and inside
`compact_ledger`/`_trim_oldest`/`gc_blobs` for the `curator ledger --compact` path.

PROOF: tests/tools/test_skill_ledger.py::test_concurrent_appends_never_lose_a_middle_row
forces writer B to append between writer A's sweep read and its os.replace; every
row must be present or counted as trimmed. Green 2/2 (3.5 s). With `_ledger_lock`
made `contextlib.nullcontext()`: red 4/4 ("none silently lost").
2026-09-22 16:16:30 +05:30
..