Repository navigation
Conversation
A hook wrapper copied into the repository's own hooks directory (as the beads backend does on init, then pointing core.hooksPath there) resolved its chain target to itself and re-executed forever while git waited for a hook exit that never came. - Refuse the chain exec when the target is this very file (-ef, which follows symlinks). - Add a re-entrancy sentinel (FM_GIT_HOOK_ACTIVE) so a hook cycle that passes through several copies exits at the first re-entry. It is applied to the commit-msg, generic client-hook, and pre-push guard wrappers. - Cover both cases with regression tests that hang without the fix.
|
Speaking as Kun's firstmate: thanks @trillium, a hook that hangs git forever is a nasty one, and both regression tests are well built. What I checked (head
The push guard still refuses under an inherited sentinel. I found no workflow, network or credential changes. All 19 checks are green on this head, the no-mistakes attestation binds it (live go 4/4), and the branch is mergeable. Why this is with the captain: part 2 changes what the default path does for any nested git call made from inside a hook. Those calls now skip their hooks silently rather than running them, so it's a new default rather than a pure restore. I'm asking the captain whether to take it as is or keep only the |
Intent
Contribute a fix upstream (kunchenguid/firstmate) so generated git hook wrappers cannot recurse into themselves and hang git. A wrapper copy placed inside the repository's own hooks directory (the beads backend does this on init and points core.hooksPath there) resolved its chain target to itself and re-executed forever while git waited for a hook exit. Decisions: refuse the chain exec when the target is this very file using -ef (follows symlinks, works with relative $0); add an FM_GIT_HOOK_ACTIVE re-entrancy sentinel so any longer hook cycle exits at the first re-entry, applied to the commit-msg wrapper, generic client-hook wrappers, and the pre-push push-guard wrapper. The sentinel deliberately has no path check because installed copies outlive their task. Two regression tests (wrapper copy inside hooksPath completes; re-entrant cycle exits at first re-entry) must hang without the fix. This is one change, one PR, cut from upstream main as a single commit. Never waive a gate; do not use --yes. Test fixtures containing 'Co-authored-by: Cursor' trailers are intentional input for the strip hook.
What Changed
bin/fm-git-strip-ai-trailers.sh: generated wrappers now refuse to exec their chain target when it is the wrapper itself (checked with-ef, which follows symlinks and works with relative$0). This covers a wrapper copy placed inside the repo's own hooks directory, which previously resolved its chain target to itself and re-executed forever while git waited.bin/fm-git-strip-ai-trailers.sh: added anFM_GIT_HOOK_ACTIVEre-entrancy sentinel to the commit-msg wrapper, the generic client-hook wrappers, and the pre-push push-guard wrapper, so any longer hook cycle exits at the first re-entry. The sentinel has no path check, because installed copies outlive their task.tests/fm-git-strip-ai-trailers.test.sh: added two regression tests, one for a wrapper copy insidecore.hooksPathcompleting and one for a re-entrant cycle exiting at the first re-entry.Risk Assessment
✅ Low: The change is a small, well-tested guard in generated hook wrappers; the push-guard pre-push wrapper keeps only the -ef self-exec guard (no inherited-sentinel bypass), and a behavioral test confirms the guard still refuses under an inherited FM_GIT_HOOK_ACTIVE.
Testing
Ran both targeted hook test suites. They drive real git commits and pushes through the installed wrappers. The wrapper-copy-in-hooksPath case completes, the re-entrant cycle exits at first re-entry, and the push guard still refuses a push with FM_GIT_HOOK_ACTIVE inherited. All passed. I did not re-run the new tests against the pre-fix code to confirm they hang there.
Evidence: strip test log
Source: strip test log
Pipeline
Updates from git push no-mistakes
✅ **intent** - passed
✅ No issues found.
✅ **Rebase** - passed
✅ No issues found.
🔧 **Review** - 1 issue found → auto-fixed ✅
bin/fm-git-strip-ai-trailers.sh:319- The sentinel exports FM_GIT_HOOK_ACTIVE=1 and every descendant of the hook inherits it. A process that a hook leaves running (a post-commit hook that starts a daemon) will skip the trailer strip and the pre-push push guard for every later git operation it runs, because each wrapper exits 0 before doing any work. The sentinel placement in the pre-push wrapper means the guard is skipped silently. The intent says the sentinel has no path check on purpose, so this is the accepted tradeoff, but it is a fail-open guard bypass. A narrower form would be to unset or reset the variable before the final chained exec, or to store a per-stack token such as the PID.🔧 Fix applied.
✅ Re-checked - no issues remain.
✅ **Test** - passed
✅ No issues found.
bash tests/fm-git-strip-ai-trailers.test.shbash tests/fm-git-push-guard.test.sh✅ **Document** - passed
✅ No issues found.
✅ **Lint** - passed
✅ No issues found.
✅ **Push** - passed
✅ No issues found.