jesseduffield/lazygit · error

Expected exactly one fixup hash, found

Error message

Expected exactly one fixup hash, found %d

What it means

Companion check in moveFixupCommitDown: it counts todos with command Pick and the fixup commit's hash and requires exactly one. Zero means the fixup commit being moved is not in the todo file; two or more means duplicated pick entries for it. The strict count protects against moving the wrong entry onto the original commit.

Solutions

  1. Verify the fixup commit still exists as a single 'pick <hash>' line in .git-rebase-todo
  2. Remove duplicate pick lines for that hash or restart the rebase if the file is inconsistent
  3. If 'found 0', the fixup was already applied — refresh lazygit before attempting the squash
Defensive patterns

Strategy: validation

Validate before calling

# sanity check before fixup: fixup hash picked exactly once
grep -cE "^pick ${FULL_HASH}" .git/rebase-merge/git-rebase-merge/git-rebase-todo 2>/dev/null || grep -cE "^pick ${FULL_HASH}" .git/rebase-merge/git-rebase-todo  # want exactly 1

Try / catch

Catch and branch on the count: 0 → fixup already applied, refresh and skip; 2+ → de-duplicate pick lines in the todo, then retry.

Prevention

When it happens

Trigger: Squashing a fixup during interactive rebase when the fixup commit is missing from .git-rebase-todo (already applied/dropped) or appears as multiple pick lines.

Common situations: Same class as the original-hash error: externally modified todo files, stale lazygit models after a conflict resolution step, duplicated picks after rebase continuations.

Related errors


AI-assisted analysis of jesseduffield/lazygit@c477a2959b (2026-08-15). Data as JSON: /api/errors/5e46c64df5792d45. Report an issue: GitHub.

Appendix: source

Thrown at pkg/utils/rebase_todo.go:263

}

func moveFixupCommitDown(todos []todo.Todo, originalHash string, fixupHash string, changeToFixup bool) ([]todo.Todo, error) {
	isOriginal := func(t todo.Todo) bool {
		return (t.Command == todo.Pick || t.Command == todo.Merge) && equalHash(t.Commit, originalHash)
	}

	isFixup := func(t todo.Todo) bool {
		return t.Command == todo.Pick && equalHash(t.Commit, fixupHash)
	}

	originalHashCount := lo.CountBy(todos, isOriginal)
	if originalHashCount != 1 {
		return nil, fmt.Errorf("Expected exactly one original hash, found %d", originalHashCount)
	}

	fixupHashCount := lo.CountBy(todos, isFixup)
	if fixupHashCount != 1 {
		return nil, fmt.Errorf("Expected exactly one fixup hash, found %d", fixupHashCount)
	}

	_, fixupIndex, _ := lo.FindIndexOf(todos, isFixup)
	_, originalIndex, _ := lo.FindIndexOf(todos, isOriginal)

	newTodos := MoveElement(todos, fixupIndex, originalIndex+1)

	if changeToFixup {
		newTodos[originalIndex+1].Command = todo.Fixup
	}

	return newTodos, nil
}

func RemoveUpdateRefsForCopiedBranch(fileName string, commentChar byte) error {
	todos, err := ReadRebaseTodoFile(fileName, commentChar)
	if err != nil {
		return err

View on GitHub (pinned to c477a2959b)