Skip to content

fix: prune stale install_meta skill/plugin rows whose files were deleted#78

Open
SherlockSalvatore wants to merge 1 commit into
RealZST:mainfrom
SherlockSalvatore:fix/prune-stale-installed-extensions
Open

fix: prune stale install_meta skill/plugin rows whose files were deleted#78
SherlockSalvatore wants to merge 1 commit into
RealZST:mainfrom
SherlockSalvatore:fix/prune-stale-installed-extensions

Conversation

@SherlockSalvatore
Copy link
Copy Markdown
Contributor

@SherlockSalvatore SherlockSalvatore commented Jun 3, 2026

Summary

Fixes #77. A skill or plugin whose files were deleted from disk (e.g. rm -rf ~/.claude) lingered forever as a ghost row in the Extensions list, still attributed to the agent it was installed under.

Root cause

sync_extensions and sync_extensions_for_agent exempted every row carrying install_meta from stale cleanup. That exemption is meant for CLI binaries, whose detection can transiently fail on startup (a CLI is resolved via PATH, which a GUI app may not fully inherit), so "missing from one scan" isn't proof it was removed. But the rule didn't look at the extension kind, so file-backed kinds (skill, plugin) were kept too β€” and a skill is a concrete file on disk, so "missing from scan" does mean the file is gone.

(Scanned MCP/hook entries carry no install_meta β€” they're entries in a shared config file β€” so they never hit this exemption and were already pruned by the normal path. This bug only affected skill/plugin.)

Fix

Narrow the exemption via a shared stale_row_should_prune predicate used by both sync paths:

  • disabled rows β†’ kept (intentionally absent from scans);
  • CLI with install_meta β†’ kept (flaky binary detection);
  • file-backed install_meta rows β†’ kept only while their source_path still exists on disk; pruned once the file is gone.

This removes ghosts after a real delete while still tolerating a transient scan gap (if the file is still on disk, the row is kept). Only the DB row is deleted β€” no on-disk files are touched. No schema change.

Test plan

  • cargo test -p hk-core β€” 492 passing, incl. three new tests:
    • test_stale_row_should_prune_decision β€” the keep/prune decision matrix
    • test_sync_prunes_ghost_skill_when_files_deleted β€” global path: ghost pruned, live skill kept, CLI kept
    • test_sync_for_agent_prunes_ghost_skill_with_install_meta β€” same through the per-agent path
    • verified the ghost tests fail on the old behavior and pass on the new
  • cargo clippy -p hk-core clean (no new warnings)

Before / After

Before (ghost row still listed under the agent):

image

After (ghost row gone after rescan):

image

sync_extensions and sync_extensions_for_agent exempted every install_meta row
from stale cleanup, so a skill or plugin whose files the user deleted (e.g.
rm -rf ~/.claude) lingered forever as a ghost row in the Extensions list.

Narrow the exemption via a shared stale_row_should_prune predicate used by both
sync paths: CLI binaries (flaky detection) stay exempt, and file-backed rows
with install_meta are kept only while their source_path still exists on disk β€” a
genuine delete is pruned, a transient scan gap is not. Scanned MCP/hook entries
carry no install_meta and were already pruned by the existing path, so this
concretely targets skill (SKILL.md) and plugin (dir) rows.

Add regression tests: decision matrix, plus ghost pruning through both the
global and per-agent sync paths.
@SherlockSalvatore SherlockSalvatore force-pushed the fix/prune-stale-installed-extensions branch from 24a1d39 to ebe4664 Compare June 4, 2026 00:55
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Deleting an agent's files (e.g. rm -rf ~/.claude) leaves ghost extension rows

1 participant