feat(install): multiplex every git hook so repo-local hook managers coexist - #9
Merged
Merged
Conversation
… coexist ghostq set a global core.hooksPath, which makes git look ONLY there and shadow every repo-local .git/hooks — silently killing lefthook/husky/pre-commit hooks, and tripping their install guards (they refuse to install, or --force clobbers ghostq's dir). Coexistence never actually held. Switch to init.templateDir: git copies ghostq's post-checkout into .git/hooks on every clone / git init, and ghostq stays out of the hooks path. git keeps using the repo's own .git/hooks, so any hook manager installs and fires untouched with zero workaround. New clones and worktrees auto-link as before; repos cloned before install take a one-time `ghostq apply`. To avoid the override silently stripping git's default template, install seeds the currently-effective template (default, or the user's own init.templateDir) via a throwaway `git init`, so clones keep .git/info/exclude, description, and sample hooks. A foreign post-checkout the template carried is preserved as post-checkout.ghostq-orig and chained. Accepted limitation: `git worktree add` runs the one shared .git/hooks/post-checkout, so a tool whose config also declares post-checkout displaces ghostq for new worktrees of that repo (manual `ghostq apply`); the clone is unaffected and all other hooks coexist with no caveat. This is a git single-slot limitation, not one ghostq can design away. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01GP3aV5RFzd8L4iQTeuACnF
lollipop-onl
force-pushed
the
claude/lefthook-coexistence-yphsnv
branch
from
July 18, 2026 04:11
73d6e03 to
b23f474
Compare
Drop the mechanism walkthrough and the templateDir single-slot caveats; keep only "your hooks work as usual" and the two cases that need a manual `ghostq apply`. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01GP3aV5RFzd8L4iQTeuACnF
The released ghostq set a global core.hooksPath pointing at ~/.config/ghostq/ hooks. Upgrading to the init.templateDir build left that config in place, so git kept looking only there — shadowing .git/hooks and silently defeating the new template hook (and the coexistence fix) for every existing user, while leaving the stale hooks dir behind. install (and uninstall) now detect a core.hooksPath still pointing at ghostq's legacy hooks dir, restore the core.hooksPath it had recorded (or unset it), and remove the stale dir. Found in self-review. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01GP3aV5RFzd8L4iQTeuACnF
The caveat was buried as a to-do bullet; label it as a Limitation, state the concrete trigger and remedy, and note that clones and all other hooks are unaffected. Keep the pre-existing-clone case as a separate one-time setup note. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01GP3aV5RFzd8L4iQTeuACnF
…OPMENT Lead with Getting started (install + adopt-your-first-file + the auto-restore payoff) so a reader can act immediately, instead of burying Install below the mechanism sections. Cut the "How it works" walkthrough down to observable behavior (trigger, symlink-each, quiet-skip) and move the hook internals (init.templateDir, seeding, null-ref gate, legacy migration) into DEVELOPMENT.md where the rest of the implementation notes live. Keep the coexistence Limitation in the README — that's a usage constraint, not an internal detail. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01GP3aV5RFzd8L4iQTeuACnF
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
A global core.hooksPath makes git look ONLY in ghostq's dir and shadows
every repo-local .git/hooks/* of every type. The dispatcher forwarded only
post-checkout, so pre-commit / pre-push / commit-msg / ... installed by
lefthook, pre-commit, husky, or plain scripts silently never fired — the
"coexists with lefthook" claim held for post-checkout alone.
This is a git-level limitation, not a lefthook quirk, so the fix is general:
ghostq installnow writes a dispatcher for every client-side hook. Each onechains to the shadowed hook (previous global hooksPath, then repo-local
.git/hooks/) and propagates a non-zero exit so gating hooks still gate;
post-checkout additionally keeps its null-ref apply gate and, as before, does
not gate on the chained exit.
Document the one wrinkle that remains lefthook-specific: its installer refuses
to target .git/hooks while core.hooksPath is set and --force would clobber
ghostq's global dir, so point it at the repo hooks dir for the install and drop
the local override afterward.
Co-Authored-By: Claude Opus 4.8 noreply@anthropic.com
Claude-Session: https://claude.ai/code/session_01GP3aV5RFzd8L4iQTeuACnF