The hook punished the only safe way to push

git refuses to check out one branch in two worktrees. So pushing a branch another worktree holds requires a detached HEAD. And in a detached worktree git rev-parse --abbrev-ref HEAD returns the literal string HEAD, which matched no prefix rule, so the version gate fired on branches that are explicitly exempt. Pick a push to see it.

What git actually sends on stdin

Before #3290
HEAD
After #3290
fix/my-branch
Version gate
fires (wrongly)

Why it mattered more than a nuisance

The gate's own message points at --no-verify as the way past it. But this same hook runs the security suite, so bypassing does not skip a version check, it relocates a security failure to CI. A false gate that nudges you toward --no-verify is worse than no gate.

The fix reads the refspec git already provides on stdin (<local ref> <local sha> <remote ref> <remote sha>) and uses the remote ref, which is the branch the push actually targets. Empty stdin falls back to the old behaviour, so a manual run is unchanged.

Proven able to fail

Reverting the stdin read (so it always falls back to HEAD) turns the new test red: resolved to 'fix/pre-push-refspec-branch', expected fix/my-branch. Restored, 16/16 pass. End to end, a simulated detached push now prints Skipping version check for fix/e2e-check. The hook still parses under /bin/bash 3.2, which Test 5 already guards.

#3290·bin/git-hooks/pre-push· found while pushing a playground for #3288