jesseduffield/lazygit · warning
Cannot squash commits in current branch: the HEAD commit is
Error message
Cannot squash commits in current branch: the HEAD commit is a merge commit or is present on the main branch.
What it means
Returned by findCommitForSquashFixupsInCurrentBranch, the 'squash all fixup commits above' helper. It uses the same predicate as the quick-start rebase check — the first commit that IsMerge() or has Status == StatusMerged must exist below index 0 — because squashing fixups internally starts an interactive rebase from that commit. When no such base exists (or it is HEAD), the operation is impossible in the current branch.
Source
Thrown at pkg/gui/controllers/local_commits_controller.go:1640
Then: func() error {
if err == nil {
self.context().SetSelectedLineIdx(targetIdx)
self.c.PostRefreshUpdate(self.context())
}
return nil
},
})
})
}
func (self *LocalCommitsController) findCommitForSquashFixupsInCurrentBranch() (*models.Commit, int, error) {
commits := self.c.Model().Commits
_, index, ok := lo.FindIndexOf(commits, func(c *models.Commit) bool {
return c.IsMerge() || c.Status == models.StatusMerged
})
if !ok || index == 0 {
return nil, -1, errors.New(self.c.Tr.CannotSquashCommitsInCurrentBranch)
}
return commits[index-1], index - 1, nil
}
// Anticipate how many commits above the selectedIdx are going to get squashed
// by the SquashAllAboveFixupCommits call, so that we can adjust the selection
// afterwards. Let's hope we're matching git's behavior correctly here.
func countSquashableCommitsAbove(commits []*models.Commit, selectedIdx int, rebaseStartIdx int) int {
result := 0
// For each commit _above_ the selection, ...
for i, commit := range commits[0:selectedIdx] {
// ... see if it is a fixup commit, and get the base subject it applies to
if baseSubject, isFixup := helpers.IsFixupCommit(commit.Name); isFixup {
// Then, for each commit after the fixup, up to and including the
// rebase start commit, see if we find the base commit
for _, baseCommit := range commits[i+1 : rebaseStartIdx+1] {View on GitHub (pinned to c477a2959b)
Solutions
- Start an interactive rebase manually from a chosen commit and squash the fixups there.
- If HEAD is a merge commit, reset or rebase past it first, then retry.
- Unguard/deepen the clone (git fetch --unshallow) so a usable base commit is visible.
Defensive patterns
Strategy: validation
Validate before calling
_, index, ok := lo.FindIndexOf(self.c.Model().Commits, func(c *models.Commit) bool {
return c.IsMerge() || c.Status == models.StatusMerged
})
if !ok || index == 0 {
// disable squash-fixups action for this branch
} Prevention
- Gate the squash-fixups menu item with the same predicate used by findCommitForSquashFixupsInCurrentBranch.
- Avoid merging to main before cleaning up fixup commits.
- Use explicit interactive rebase from a selected commit when the guard trips.
When it happens
Trigger: Invoking squash-fixups-in-current-branch when lo.FindIndexOf(commits, IsMerge || StatusMerged) yields !ok or index == 0 — i.e. HEAD is a merge commit or is already merged into the main branch.
Common situations: Branch fully merged to main with only fixup commits above; HEAD is a merge commit (e.g. after merging a PR locally); shallow clone hiding the merge base.
Related errors
- Expected exactly one original hash, found %d
- Expected exactly one fixup hash, found %d
- Cannot start interactive rebase: the HEAD commit is a merge
- index outside of range of commits
- Feature not available for users using GPG. If you are using
AI-assisted analysis of jesseduffield/lazygit@c477a2959b (2026-08-15).
Data as JSON: /api/errors/f4ab9e04618cf088.
Report an issue: GitHub.