fix(plugin): a compaction clears every session ledger, not the two on the list (#4101)
CI & Build / Python lint (push) Successful in 2s
CI & Build / Plugin hooks (push) Successful in 9s
CI & Build / integration (push) Successful in 49s
CI & Build / TypeScript typecheck (push) Successful in 53s
CI & Build / Python tests (push) Failing after 1m4s
CI & Build / Build & push image (push) Skipped

`scribe_session_context.sh` cleared `.rules.ids` and `.opened.ids` by name and
left `.ids`, `.sync.ids` and `.derive.ids` standing, under a comment asserting
that was a decision. Reading the note arms says it was not: their exclusions go
straight into `semantic_search_notes`, so a surfaced note leaves the result set
rather than being rendered as a reference the way #3750 gave a repeated rule,
and unlike the rules ledger they never age. Hard, permanent, never cleared — a
note surfaced in a session's first minute is unreachable for the rest of it,
which is milestone 386's own defect alive on the arms that fire most often.

The list was the bug, so the fix is not a longer list. `scribe_clear_session_
ledgers` matches the naming convention instead — a per-session ledger is
`<sid>[.<kind>].ids` — which covers all five and covers the sixth on the day it
is written. `<sid>.unreached` is deliberately outside it: that records an
outage, not held context, and #2932 needs it to survive.

tests/test_session_ledger_clear.py runs the hook rather than grepping it for
`rm -f`, since grepping for the names is the pattern being removed. It pins
both directions — `compact`/`clear` take all five, `startup`/`resume` take
none — plus the convention the glob rests on, checked against the hooks
themselves so a ledger named outside it fails loudly instead of silently never
clearing.

Also drops a stale comment pointing at a rules-etag marker that milestone 394
retired.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01821k5B3Ysecp9fNYs92Kuy
This commit is contained in:
2026-09-16 21:00:42 -04:00
co-authored by Claude Opus 5
parent 957a72c501
commit c61f7301bc
5 changed files with 229 additions and 33 deletions
+3 -15
View File
@@ -135,21 +135,9 @@ def test_an_event_with_nothing_usable_records_nothing_and_still_exits_zero(event
assert not led.exists()
# ── the two ledgers stay in step ───────────────────────────────────────────
def test_a_compaction_clears_both_ledgers():
"""The one that would be worst to get wrong. `.opened.ids` describes a
context the compaction just destroyed, so keeping it while clearing the
naming ledger would have the arms telling a freshly-summarised session
"you opened it earlier" about a rule now nowhere in its context.
"""
sh = (HOOKS / "scribe_session_context.sh").read_text()
block = sh.split("case \"$source\" in")[1].split("esac")[0]
assert "compact|clear)" in block
for led in (".rules.ids", ".opened.ids"):
assert re.search(rf"rm -f .*{re.escape(led)}", block), (
f"{led} survives a compaction that destroyed what it describes"
)
# What a compaction clears — including `.opened.ids`, whose claim is the one
# that would be worst to get wrong — is asserted against the running hook in
# tests/test_session_ledger_clear.py, so there is one home for it.
def test_the_recorder_is_registered_on_the_get_rule_tool():