Description
When git commit is run via prek with unstaged changes present (in files outside the staged set), prek stashes the unstaged changes before running hooks. If a hook like end-of-file-fixer or trailing-whitespace modifies staged files, prek detects a conflict between the stash and the hook modifications, aborts the commit, and rolls back.
The problem: after the rollback, the git index is left in a corrupted state. Specifically, newly added files can have their content emptied in the index. If the user then re-stages and commits (especially after resolving the unstaged changes), the corrupted index content is committed — resulting in empty files being committed silently.
Steps to reproduce
- Have a repo with a
.pre-commit-config.yaml that includes auto-fixing hooks (end-of-file-fixer, trailing-whitespace, etc.)
- Create a new file (e.g.,
new-file.yaml) with content that has trailing whitespace or a missing final newline
- Modify an existing file (e.g.,
existing.py) with similar minor formatting issues
- Stage only a subset of the repo:
git add some-directory/ — leaving at least one modified file elsewhere unstaged
- Run
git commit -m "feat: add new files"
Expected: the commit either succeeds (with hooks auto-fixing the formatting) or fails cleanly with the index intact.
Actual:
- prek stashes the unstaged changes
- Hooks fix the staged files on disk
- prek detects a conflict between the stash and hook modifications
- prek rolls back and prints:
Stashed changes conflicted with changes made by hook, rolling back the hook changes
- The commit is aborted
- The git index is left corrupted — newly added files may now be empty in the index (hash
e69de29b, the empty blob)
If the user then re-stages and eventually commits, the file goes in empty.
Terminal output from real occurrence
$ git add api/
$ git commit -m "feat: add email security bl for nl and be markets"
Unstaged changes detected, stashing unstaged changes to `/Users/user/.cache/prek/patches/1775206900588-36748.patch`
Files were modified by following hooks.................................................Failed
┌ check yaml.........................................................................Passed
│ check json.....................................................(no files to check)Skipped
│ check for merge conflicts..........................................................Passed
│ fix end of files...................................................................Failed
│ - hook id: end-of-file-fixer
│ - exit code: 1
│
│ Fixing api/riskmgt/models/business_line/questionnaire_version.py
│ Fixing api/static/questionnaires/elements/answers/email-security-policy-options.yaml
│ Fixing api/static/questionnaires/NL_CYBER_EMAIL_SECURITY/260403.yaml
│ Fixing api/static/business-lines/BE_CYBER_EMAIL_SECURITY/v01.yaml
│ Fixing api/riskmgt/models/business_line/business_line_version.py
└ trim trailing whitespace...........................................................Failed
│ - hook id: trailing-whitespace
│ - exit code: 1
│
│ Fixing api/static/business-lines/BE_CYBER_EMAIL_SECURITY/v01.yaml
│ Fixing api/riskmgt/models/business_line/business_line_version.py
[... more hooks pass ...]
Stashed changes conflicted with changes made by hook, rolling back the hook changes
Restored working tree changes from `/Users/user/.cache/prek/patches/1775206900588-36748.patch`
After repeating git add api/ + git commit a second time (same result), the user staged everything with git add . and committed. The commit succeeded, but BE_CYBER_EMAIL_SECURITY/v01.yaml was committed as a 0-byte empty file despite having 18 lines of YAML content on disk.
Proof:
$ git show HEAD -- api/static/business-lines/BE_CYBER_EMAIL_SECURITY/v01.yaml
# Shows: new file mode 100644, index 00000000..e69de29b (empty blob)
Additionally, on the second git commit attempt, the type checker (ty check) ran against the corrupted index and reported 32 unresolved-import errors for business_line_version.py — a file that clearly had content — confirming the index was corrupted.
Proposed fix
When prek detects a conflict between the stash and hook modifications and decides to roll back, it should also restore the git index to its exact pre-hook state, not just the working tree. Currently it appears that:
- Hooks modify files on disk AND those changes get reflected in the index
- The rollback restores the working tree from the patch
- But the index is not restored to its pre-hook snapshot
Saving and restoring a snapshot of the index (e.g., via git write-tree before hooks run, then git read-tree during rollback) would prevent the corruption.
Alternatively, prek could re-stage the restored working tree files into the index after rollback, so the index matches the working tree.
Environment
- prek 0.3.6 (Homebrew 2026-03-16)
- git 2.50.1 (Apple Git-155)
- macOS 26.3.1
Description
When
git commitis run via prek with unstaged changes present (in files outside the staged set), prek stashes the unstaged changes before running hooks. If a hook likeend-of-file-fixerortrailing-whitespacemodifies staged files, prek detects a conflict between the stash and the hook modifications, aborts the commit, and rolls back.The problem: after the rollback, the git index is left in a corrupted state. Specifically, newly added files can have their content emptied in the index. If the user then re-stages and commits (especially after resolving the unstaged changes), the corrupted index content is committed — resulting in empty files being committed silently.
Steps to reproduce
.pre-commit-config.yamlthat includes auto-fixing hooks (end-of-file-fixer,trailing-whitespace, etc.)new-file.yaml) with content that has trailing whitespace or a missing final newlineexisting.py) with similar minor formatting issuesgit add some-directory/— leaving at least one modified file elsewhere unstagedgit commit -m "feat: add new files"Expected: the commit either succeeds (with hooks auto-fixing the formatting) or fails cleanly with the index intact.
Actual:
Stashed changes conflicted with changes made by hook, rolling back the hook changese69de29b, the empty blob)If the user then re-stages and eventually commits, the file goes in empty.
Terminal output from real occurrence
After repeating
git add api/+git commita second time (same result), the user staged everything withgit add .and committed. The commit succeeded, butBE_CYBER_EMAIL_SECURITY/v01.yamlwas committed as a 0-byte empty file despite having 18 lines of YAML content on disk.Proof:
Additionally, on the second
git commitattempt, the type checker (ty check) ran against the corrupted index and reported 32unresolved-importerrors forbusiness_line_version.py— a file that clearly had content — confirming the index was corrupted.Proposed fix
When prek detects a conflict between the stash and hook modifications and decides to roll back, it should also restore the git index to its exact pre-hook state, not just the working tree. Currently it appears that:
Saving and restoring a snapshot of the index (e.g., via
git write-treebefore hooks run, thengit read-treeduring rollback) would prevent the corruption.Alternatively, prek could re-stage the restored working tree files into the index after rollback, so the index matches the working tree.
Environment