Skip to content

Commit 642a89d

Browse files
committed
fix(skill-cache): purge compacts for mixed-case skill names on invalidation
invalidate_for_path built the compact-purge suffix from the un-lowercased meta.skill_name, but _compact_file_id (the writer) lowercases the safe-name segment. For a mixed-case namespaced skill such as userSettings:brainstorming, the on-disk compact is named ...-usersettings_brainstormingn-compact while the purge suffix was ...-userSettings_brainstormingn-compact, so the endswith match missed and the stale compact survived a skill edit — a later --compact recall served pre-edit content. Lowercase the safe name in the purger to mirror the writer exactly.
1 parent 3eddf36 commit 642a89d

2 files changed

Lines changed: 33 additions & 1 deletion

File tree

src/token_goat/skill_cache.py

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -999,7 +999,12 @@ def invalidate_for_path(file_path: str) -> int:
999999
continue
10001000
# Match found: collect the compact suffix pattern BEFORE removing files
10011001
# so the suffix is available even after the body .txt is deleted.
1002-
safe_name = meta.skill_name.replace(":", "_")
1002+
# Must mirror _compact_file_id exactly — it lowercases the safe name, so
1003+
# a mixed-case skill name (e.g. "userSettings:brainstorming") writes a
1004+
# compact file as "...-usersettings_brainstormingn-compact". Without the
1005+
# same .lower() here the purge suffix never matches and the stale compact
1006+
# survives a skill edit.
1007+
safe_name = meta.skill_name.lower().replace(":", "_")
10031008
if ":" in meta.skill_name:
10041009
safe_name += "n"
10051010
compact_suffixes.add(f"-{safe_name}-compact")

tests/test_skill_worker_invalidation.py

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -130,6 +130,33 @@ def test_compact_removal_with_namespaced_skill(self, tmp_data_dir):
130130
compact_after = [f for f in cache_dir.iterdir() if f.name.endswith("-compact")]
131131
assert len(compact_after) == 0
132132

133+
def test_compact_removal_with_mixed_case_skill(self, tmp_data_dir):
134+
"""Compact removal works for mixed-case namespaced names (regression).
135+
136+
_compact_file_id lowercases the safe-name segment, so a skill named
137+
"userSettings:brainstorming" writes its compact as
138+
"...-usersettings_brainstormingn-compact". invalidate_for_path previously
139+
built the purge suffix from the un-lowercased meta.skill_name, so the
140+
suffix ("...-userSettings_brainstormingn-compact") never matched the
141+
on-disk file and the stale compact survived the edit. Fails pre-fix
142+
(compact_after == 1), passes post-fix (compact_after == 0).
143+
"""
144+
source = "/plugins/core/skills/brainstorming/SKILL.md"
145+
meta = skill_cache.store_output(
146+
"sess_mc", "userSettings:brainstorming", "body " * 200, source_path=source
147+
)
148+
assert meta is not None
149+
skill_cache.write_sidecar(meta)
150+
skill_cache.store_compact("sess_mc", "userSettings:brainstorming", "compact body")
151+
cache_dir = tmp_data_dir / "skills"
152+
compact_before = [f for f in cache_dir.iterdir() if f.name.endswith("-compact")]
153+
assert compact_before, "compact file should exist before invalidation"
154+
155+
n = skill_cache.invalidate_for_path(source)
156+
assert n == 1
157+
compact_after = [f for f in cache_dir.iterdir() if f.name.endswith("-compact")]
158+
assert len(compact_after) == 0, "stale compact for mixed-case skill was not purged"
159+
133160
def test_other_skills_not_removed(self, tmp_data_dir):
134161
"""Only entries matching the given path are removed; others are untouched."""
135162
source_a = "/skills/ralph/SKILL.md"

0 commit comments

Comments
 (0)