Skip to content

fix(hooks): merge PreToolUse bundles instead of replacing#2

Draft
Corey-T1000 wants to merge 1 commit intoauthzed:mainfrom
Corey-T1000:fix/install-hooks-merge-not-replace
Draft

fix(hooks): merge PreToolUse bundles instead of replacing#2
Corey-T1000 wants to merge 1 commit intoauthzed:mainfrom
Corey-T1000:fix/install-hooks-merge-not-replace

Conversation

@Corey-T1000
Copy link
Copy Markdown

@Corey-T1000 Corey-T1000 commented Apr 20, 2026

Summary

InstallHooksConfig currently assigns a fresh single-entry slice to hooks.PreToolUse, which silently wipes any existing hooks (guard scripts, user-defined hooks, other plugins' hooks) every time spicebox serve-claude --install-hooks runs.

This PR changes InstallHooksConfig to merge: preserve existing bundles, dedup any prior SpiceBox bundle (identified by hookURL) so re-installs stay idempotent, and append ours last so user guards run first and SpiceBox's /check gets the final say.

Why

Users running SpiceBox alongside other Claude Code hooks lose them on first --install-hooks. This is a silent data-loss regression in a security-critical config file.

Test plan

  • _PreservesExistingPreToolUseBundles — pre-existing guard survives install
  • _DedupesOnReinstall — repeated installs don't duplicate the SpiceBox bundle
  • _ReinstallPreservesOtherBundles — guards and a single SpiceBox entry coexist across re-installs
  • Existing install/uninstall tests continue to pass after assertHookURL refactor (scans array by URL instead of index 0)
  • gofmt, go vet, go build, go test ./internal/lifecycle/... all clean

Notes

  • assertHookURL had to be updated because the SpiceBox bundle is no longer guaranteed to be at index 0 — it's appended last so user hooks get first crack.
  • Dedup uses URL equality on any (requires both sides string), so malicious settings can't trick it into dropping unrelated entries; worst case it fails to dedup and the user sees duplicates, never silent data loss.
  • Type switch handles both []any (disk round-trip shape) and []map[string]any (in-memory construction) for symmetry with the pre-refactor tolerance.

🤖 Generated with Claude Code

InstallHooksConfig used to assign a single-entry slice to hooks.PreToolUse,
silently wiping any existing guard hooks (security scanners, subagent
auto-approvers, commit formatters, etc.) whenever serve-claude ran with
--install-hooks.

Now it preserves existing bundles, dedups any prior SpiceBox bundle
(identified by hookURL) to stay idempotent across re-installs, and appends
ours last so user guards run first and SpiceBox's /check gets the final
say.

Adds three regression tests:
- _PreservesExistingPreToolUseBundles — existing guard survives install
- _DedupesOnReinstall — no duplicate SpiceBox entries on repeated installs
- _ReinstallPreservesOtherBundles — guards and single SpiceBox coexist

assertHookURL was updated to scan for the hook by URL rather than assuming
index 0, since the SpiceBox bundle is no longer guaranteed to be first.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@Corey-T1000 Corey-T1000 force-pushed the fix/install-hooks-merge-not-replace branch from d1303f8 to a24b39d Compare April 20, 2026 16:17
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