Skip to content

fix(hooks): remove the old entry when a hook event or matcher changes - #316

Merged
Minitour merged 9 commits into
fix/secret-migration-305from
fix/stale-hook-entries-306
Oct 15, 2026
Merged

Minitour merged 9 commits into
fix/secret-migration-305from
fix/stale-hook-entries-306

Conversation

@Minitour

@Minitour Minitour commented Oct 14, 2026 •

Copy link
Copy Markdown
Member

Summary

Changing a hook's on or matcher left the old provider entry running. capa upserted entries by event and matcher, so the new capa:<id> entry was added next to the old one. The database locator then pointed only at the new entry, so neither capa install nor capa clean removed the old one. Closes #306.

Stack 2/5: base #315, next #317.

What changed

  • Before placing an entry, upsertHookEntry drops entries tagged with the same capa:<id> that are outside the target:
    • Claude-like shapes (Claude Code, Codex, Gemini CLI, Windsurf): other events and other matcher groups;
    • Cursor: other events.
  • An entry already in the target group stays where it is, so a re-install doesn't reorder the file.
  • User-authored entries are never touched (only exact capa:<id> tags are matched).
  • Antigravity is keyed by hook name and was not affected.
  • resolveMatcher now returns "" instead of undefined when a mapping carries an explicit matcherPrefix: undefined.

Test plan

  • Claude:
    • an event change leaves one entry, and the user's entry in the old group is untouched;
    • a matcher change moves the entry;
    • a re-install keeps sibling order.
  • Cursor: an event change leaves one entry.
  • Existing hook handler and hook install tests.
  • bunx tsc --noEmit

Checklist

  • Tests added or updated
  • bunx tsc --noEmit passes
  • Docs updated (if user-facing): not needed

🤖 Generated with Claude Code

Hook entries were upserted by event and matcher, so changing a hook's
`on` or `matcher` added a new capa:<id> entry and left the old one
running. The database locator then pointed only at the new entry, so
neither install nor capa clean removed the old one.

Before placing an entry, capa now drops entries tagged with the same
capa:<id> in other events (and, for Claude-like shapes, other matcher
groups). An entry already in the target group stays where it is, so
re-installs don't reorder the file. Antigravity is keyed by hook name
and was not affected.

resolveMatcher also returns "" instead of undefined when a mapping
carries an explicit undefined matcherPrefix.

Closes #306
Skill, plugin and hook entries pass def.version, def.ref (and repo
suffixes) to `git rev-parse --verify <ref>^{commit}`. Nothing checked
them first, so rev-parse's revision syntax (HEAD~2, main@{1}, :/text,
a..b) and option-like values reached git.

resolveRef, the only place these values reach git, now accepts only
plain tag/branch names and SHAs (letters, digits, . _ / + -, no leading
"-", no "..") and fails with a clear error otherwise. Lockfile pins
are checked the same way.

Closes #308
The capabilities-manager reference still described ${PLUGIN_DATA} as
~/.capa/plugin-data/<projectId>/<pluginId>/ that survives plugin updates.
Since #286 the folder is <slug>-<hash>, keyed by the declared source, and
it is removed when the plugin is no longer declared and by capa clean.
Match docs/README.md.

Closes #309
removeTaggedElsewhere deleted every empty event array in the hooks
object, including ones it never touched (a user's "Stop": [] or a
Gemini disabled: []). Only delete an array this call emptied.
The allowlist rejected tags git accepts, such as changesets/lerna
monorepo tags (@scope/pkg@1.2.3), "feature#12" or unicode names, which
installed fine before. Reject what git check-ref-format rejects instead
(control characters, space, ~ ^ : ? * [ \, "..", "@{", a leading
"-", "/" or ".", and similar), which still rules out rev-parse's
revision syntax and option-like values.

The offline snapshot fast path only checked that a lockfile pin was 40
characters long before using it as a path segment, so a tampered
resolvedRef could point at another local directory. It now requires 40
hex characters; anything else falls through to resolveRef, which
rejects it.
docs(skills): correct the PLUGIN_DATA path and lifetime
fix(cache): validate git version/ref values before rev-parse
@Minitour
Minitour merged commit 5d1b8a0 into fix/secret-migration-305 Oct 15, 2026
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.

1 participant