Re-install discards another tool's newer hook and chains to a stale .pre-entire backup
Nobody has claimed this yet.
- Dominant language
- Go
- Stars
- 5.1k
- Forks
- 475
- Avg merge
- 1d 11h
- Merged PRs (30d)
- 178
Description
What happened?
When another tool that manages git hooks reinstalls its hook at a name Entire wraps (commit-msg, prepare-commit-msg, post-commit, pre-push, post-rewrite), the next Entire hook install discards that tool's new hook and chains to the old copy in <hook>.pre-entire. Git then runs the stale copy on every commit. The only signal is one stderr line, which is invisible when the reinstall happens through the agent-hook self-heal path (EnsureSetup on user-prompt-submit) rather than an interactive entire enable.
Where it comes from (cmd/entire/cli/strategy/hooks.go, InstallGitHook, at fce049c):
if existingErr == nil && !strings.Contains(string(existing), entireHookMarker) {
if !backupExists {
os.Rename(hookPath, backupPath) // first install: back up the foreign hook
} else {
fmt.Fprintf(os.Stderr, "[entire] Warning: replacing %s (backup %s%s already exists from a previous install)\n", ...)
// the foreign hook now at hookPath is overwritten below and never saved
}
backupExists = true
}
TestInstallGitHook_DoesNotOverwriteExistingBackup pins this: the backup is never refreshed once it exists. That protects the first backup, but it means the file at the hook name, which is the other tool's current hook, is the one that gets lost. The chain then runs whatever the other tool installed at the time of the first Entire install, forever.
This is the mirror image of #1349: there, the other tool reclaims the name and Entire's wrapper is lost; here, Entire reclaims the name and the other tool's newer hook is lost, silently replaced by an older version of itself.
Steps to reproduce
git init -q -b main repro && cd repro
git commit -q --allow-empty -m init
# another tool installs its hook (v1)
printf '#!/bin/sh\necho "tool A v1 ran" >> "$(git rev-parse --git-dir)/hook-log"\n' > .git/hooks/commit-msg
chmod +x .git/hooks/commit-msg
entire enable --agent claude-code --no-github --local
# -> "[entire] Backed up existing commit-msg to commit-msg.pre-entire" (correct)
# the other tool reinstalls a newer hook (v2), replacing Entire's wrapper
printf '#!/bin/sh\necho "tool A v2 ran" >> "$(git rev-parse --git-dir)/hook-log"\n' > .git/hooks/commit-msg
chmod +x .git/hooks/commit-msg
entire enable --force --agent claude-code --no-github --local
# -> "[entire] Warning: replacing commit-msg (backup commit-msg.pre-entire already exists from a previous install)"
tail -1 .git/hooks/commit-msg.pre-entire # still v1
git commit -q --allow-empty -m probe
cat .git/hook-log # "tool A v1 ran" <- v2 is gone
Expected: after the second install, the chain runs v2 (the hook that was actually at the name), or Entire refuses to overwrite a foreign hook while a backup exists and says what to do.
Suggested fix, either of:
- When the file at the hook name has no Entire marker and differs from the existing backup, replace the backup with it before installing the wrapper (the backup should hold the most recent foreign hook, not the first one ever seen). Keep the current behaviour only when the two are byte-identical.
- Or: never overwrite a foreign hook while a backup exists; print the warning and skip that name, so nothing is lost and the user can resolve it.
Also worth surfacing in entire doctor / entire status: "hook <name> is chained to a backup that no longer matches what another tool last installed".
Entire CLI version
Entire CLI 0.10.3
OS and architecture
Darwin 25.6.0 arm64
Additional context
Reproduced with the exact commands above on a fresh repository; nothing project-specific is involved. Git 2.50.1.
Contributor guide
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- Open a pull request that references the issue number.
Research direction
Start in cmd/entire/cli/strategy/hooks.go at InstallGitHook and run TestInstallGitHook_DoesNotOverwriteExistingBackup, then reproduce the two-install sequence from the issue with a foreign commit-msg hook. Done means a newer foreign hook is not silently discarded: the chain runs the hook currently at the name, or installation refuses that name with clear guidance.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- git, go
- Domain
- cli, devtools
- Issue type
- Bug
- Difficulty
- 3/5
- Estimated time
- 1-2 days
- Activity status
- Active
- Clarity
- Clearly specified
- Newbie friendliness
- 78/100