Skip to content

Stash conflict rollback corrupts git index, causing newly added files to be committed empty #1889

@Alex-Dufour

Description

@Alex-Dufour

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

  1. Have a repo with a .pre-commit-config.yaml that includes auto-fixing hooks (end-of-file-fixer, trailing-whitespace, etc.)
  2. Create a new file (e.g., new-file.yaml) with content that has trailing whitespace or a missing final newline
  3. Modify an existing file (e.g., existing.py) with similar minor formatting issues
  4. Stage only a subset of the repo: git add some-directory/ — leaving at least one modified file elsewhere unstaged
  5. 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:

  1. Hooks modify files on disk AND those changes get reflected in the index
  2. The rollback restores the working tree from the patch
  3. 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

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions