You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
pre-push hard-fails and blocks the push in any clone where the remote's default branch does not exist as a local branch, and the branch being pushed has no resolvable upstream (i.e. the normal first push of a new feature branch).
git diff --name-only HEAD @{push} — fails on a branch with no upstream.
resolveHeadBranch() reads .git/refs/remotes/origin/HEAD and returns the bare branch name (main), then runs git diff --name-only HEAD main --.
If no head branch can be resolved at all → git ls-tree -r --name-only HEAD.
Step 2 reads the remote's default branch name but then diffs against the local branch of that name. If that local branch doesn't exist, git exits 128 with fatal: bad revision 'main'. The error is returned rather than treated as a skip, so every job in the hook fails and the push is rejected.
The push-files computation runs unconditionally for pre-push as the "skip if nothing to push" gate in buildCommand (HookUsesPushFiles), so this happens even when no job references {push_files} or {files} at all — as in the config below, which is two whole-project typechecks. The computed list is only tested for emptiness and then discarded, so the hook dies on a value it never uses.
Setting a hook-level or command-level files: does not help — that only feeds the {files} template and does not replace the built-in push-files source.
Note that step 3 is a graceful fallback (ls-tree always works). It's only reachable when origin/HEAD is absent, so a clone with less remote information works fine while a normal clone with origin/HEAD set fails. Deleting refs/remotes/origin/HEAD is currently a working (if perverse) workaround.
Related: #1454 covers the same fallback picking a stale local default branch (wrong file list); this report is the harder failure where the local branch is missing entirely (push blocked). #1396 previously fixed the pathspec ambiguity on this same line.
lefthook.yml
pre-push:
commands:
probe:
run: echo RAN
No {push_files} / {files} template anywhere — the failure does not depend on one.
Commands to reproduce
export LEFTHOOK_VERBOSE=true
# a remote whose default branch is `main`
git init --bare -q -b main remote.git
git init -q -b main seed &&cd seed
echo hi > file.txt && git add -A && git commit -q -m init
git remote add origin ../remote.git && git push -q origin main
cd ..
git clone -q remote.git work &&cd work
printf'pre-push:\n commands:\n probe:\n run: echo RAN\n'> lefthook.yml
git add -A && git commit -q -m cfg
lefthook install
# a clone that has no local `main` — e.g. someone who always branches# straight off `origin/main` and doesn't keep a local default branch
git switch -q -c feature
git branch -D main
git push origin feature
Output:
╭─────────────────────────────────────╮
│ lefthook v2.1.10 hook: pre-push │
╰─────────────────────────────────────╯
│ [lefthook] git: git diff --name-only HEAD @{push}
│ > git diff --name-only HEAD @{push}
│ fatal: no upstream configured for branch 'feature'
│
│ [lefthook] git: git diff --name-only HEAD main --
│ > git diff --name-only HEAD main --
│ fatal: bad revision 'main'
│
│ probe (skip) exit status 128
────────────────────────────────────
summary: (done in 0.05 seconds)
✗ probe: exit status 128 (0.05 seconds)
error: failed to push some refs to '.../remote.git'
git branch main origin/main in the clone makes the same push succeed, which confirms the cause. The local branch never needs to be current — it only needs to exist.
Lefthook version
2.1.10 8d9cfec5f52367af6374a5430b4e9568844856e5 (also reproduced on 2.1.9)
Possible solution
Any of these would fix it, roughly in order of preference:
Fall through to the existing git ls-tree -r --name-only HEAD branch when the fallback diff fails, instead of propagating the error — the graceful path already exists one branch up.
At minimum, don't fail the whole hook on it: a push-files computation that no job consumes should not be able to reject a push.
Description
pre-pushhard-fails and blocks the push in any clone where the remote's default branch does not exist as a local branch, and the branch being pushed has no resolvable upstream (i.e. the normal first push of a new feature branch).Repo.PushFiles()(internal/git/repo.go#L189) falls back like this:git diff --name-only HEAD @{push}— fails on a branch with no upstream.resolveHeadBranch()reads.git/refs/remotes/origin/HEADand returns the bare branch name (main), then runsgit diff --name-only HEAD main --.git ls-tree -r --name-only HEAD.Step 2 reads the remote's default branch name but then diffs against the local branch of that name. If that local branch doesn't exist, git exits 128 with
fatal: bad revision 'main'. The error is returned rather than treated as a skip, so every job in the hook fails and the push is rejected.The push-files computation runs unconditionally for
pre-pushas the "skip if nothing to push" gate inbuildCommand(HookUsesPushFiles), so this happens even when no job references{push_files}or{files}at all — as in the config below, which is two whole-project typechecks. The computed list is only tested for emptiness and then discarded, so the hook dies on a value it never uses.Setting a hook-level or command-level
files:does not help — that only feeds the{files}template and does not replace the built-in push-files source.Note that step 3 is a graceful fallback (
ls-treealways works). It's only reachable whenorigin/HEADis absent, so a clone with less remote information works fine while a normal clone withorigin/HEADset fails. Deletingrefs/remotes/origin/HEADis currently a working (if perverse) workaround.Related: #1454 covers the same fallback picking a stale local default branch (wrong file list); this report is the harder failure where the local branch is missing entirely (push blocked). #1396 previously fixed the pathspec ambiguity on this same line.
lefthook.ymlNo
{push_files}/{files}template anywhere — the failure does not depend on one.Commands to reproduce
Output:
git branch main origin/mainin the clone makes the same push succeed, which confirms the cause. The local branch never needs to be current — it only needs to exist.Lefthook version
2.1.10 8d9cfec5f52367af6374a5430b4e9568844856e5(also reproduced on 2.1.9)Possible solution
Any of these would fix it, roughly in order of preference:
origin/main) rather than the bare local name, sinceorigin/HEADis where the name came from. This also addresses the staleness in {push_files} can include stale local default-branch files when @{push} is unavailable #1454.git ls-tree -r --name-only HEADbranch when the fallback diff fails, instead of propagating the error — the graceful path already exists one branch up.