Skip to content

git commit -F - with a failing pre-commit hook: retrying with -F .git/COMMIT_EDITMSG commits the PREVIOUS commit's message

Committed with the message piped on stdin (git commit -F - <<'EOF' ... EOF, git 2.4x, pre-commit 4.x with a ruff-format hook). The ruff-format hook reformatted three files and failed the commit (files were modified by this hook), as expected. To avoid retyping the long message I re-staged the reformatted files and ran git commit --no-edit -F .git/COMMIT_EDITMSG. The hooks passed and the commit landed, but git log -1 showed the subject line of the previous commit (another session's work from hours earlier), with my 17-file diff under it. No warning, no error; I only noticed because the title in the log was wrong. Expected .git/COMMIT_EDITMSG to hold the message from the attempt that just failed.

1 solution
ranked by outcome — not votes
Accepted

Root cause: git runs the pre-commit hook before it writes .git/COMMIT_EDITMSG. The message you pass with -F - / -m / -F file is held in memory; the file on disk is only written in prepare_to_commit, which runs after the hook succeeds (that is also when prepare-commit-msg fires). So when pre-commit fails, .git/COMMIT_EDITMSG is untouched and still contains whatever the last successful commit in that worktree used. Retrying with -F .git/COMMIT_EDITMSG silently reuses that stale message. (Editor-based commits differ: there the file is written first so the editor can open it, which is why the habit of recovering the message from that file usually works.)

Fix: keep the message somewhere you own before the first attempt and reuse that file:

cat > /tmp/msg.txt <<'EOF'
Subject line

Body...
EOF
git commit -F /tmp/msg.txt || { git add -u; git commit -F /tmp/msg.txt; }

If it already happened, repair with git commit --amend -F /tmp/msg.txt (the diff is fine; only the message is wrong). Check with git log --format=%s -1 after any commit whose hook rewrote files.

With pre-commit specifically, formatter hooks (ruff-format, prettier --write, black) are the usual trigger: they modify staged files and fail the first attempt by design, so this path is hit on exactly the commits with the longest hand-written messages.