Summary
lefthook's pre-push lanes silently skip on explicit-refspec pushes — git push <remote> <branch>:<branch> — because push files are derived from the checked-out HEAD's diff. A contributor pushing that way gets zero pre-push gates and a log that reads like normal operation.
Reproduction
From a checkout where HEAD has no diff against origin/main:
git push <url> refs/heads/some-branch:refs/heads/some-branch
Every lane reports (skip) no matching push files — including branch-skew and push-head-scope, which carry no files:/glob: of their own. Exit 0, push proceeds.
Why this is worth fixing rather than documenting
AGENTS.md notes that non-HEAD ref pushes "rely on CI for their path-scoped checks", so the behaviour is known. The problem is the signal: the output is indistinguishable from a run where every lane genuinely had nothing to do. Someone pushing branch:branch — an ordinary thing to do — reasonably believes the hooks ran.
We hit this while pushing several branches by refspec and initially credited the hooks with checks they had not performed. Every "gates green" we reported came from running the gates explicitly; the hook contributed nothing.
Suggested direction
Either make lanes without files:/glob: run unconditionally regardless of push shape, or emit a distinct non-skip message when the push shape means no lane can evaluate — so "nothing to check" and "cannot check" are distinguishable.
🤖 Generated with Claude Code
Summary
lefthook'spre-pushlanes silently skip on explicit-refspec pushes —git push <remote> <branch>:<branch>— because push files are derived from the checked-out HEAD's diff. A contributor pushing that way gets zero pre-push gates and a log that reads like normal operation.Reproduction
From a checkout where
HEADhas no diff againstorigin/main:Every lane reports
(skip) no matching push files— includingbranch-skewandpush-head-scope, which carry nofiles:/glob:of their own. Exit 0, push proceeds.Why this is worth fixing rather than documenting
AGENTS.mdnotes that non-HEAD ref pushes "rely on CI for their path-scoped checks", so the behaviour is known. The problem is the signal: the output is indistinguishable from a run where every lane genuinely had nothing to do. Someone pushingbranch:branch— an ordinary thing to do — reasonably believes the hooks ran.We hit this while pushing several branches by refspec and initially credited the hooks with checks they had not performed. Every "gates green" we reported came from running the gates explicitly; the hook contributed nothing.
Suggested direction
Either make lanes without
files:/glob:run unconditionally regardless of push shape, or emit a distinct non-skip message when the push shape means no lane can evaluate — so "nothing to check" and "cannot check" are distinguishable.🤖 Generated with Claude Code