Handling partially staged files Jump to heading

A partially staged file is one where some changes are in the index and others are only in the working tree — the result of git add -p, or of editing a file after staging it. Formatters see only one version of a file, so lint-staged has a problem: it must run tools on exactly what will be committed, apply their fixes to the index, and leave your unstaged work untouched. It does this by temporarily hiding unstaged changes, running the tasks, then restoring them. Usually it works invisibly. Occasionally the formatter’s fixes overlap your unstaged edits and the restore conflicts, and your unstaged work seems to vanish. It has not; it is in a backup stash. This page explains the mechanism and the recovery, within lint-staged formatting automation.

When to use this approach Jump to heading

  • You stage parts of files with git add -p and use lint-staged.
  • After a commit, unstaged changes in a file disappeared or came back with conflict markers.
  • lint-staged reported “Unstaged changes could not be restored due to a merge conflict”.
  • You want to understand lint-staged’s safety mechanism before relying on it, as introduced in running Prettier and ESLint only on staged files.

Step 1 — See what partial staging looks like Jump to heading

A file is partially staged when both the index and the working tree differ from HEAD, and from each other.

git add -p src/billing/totals.py       # stage only the first hunk
git status --short src/billing/totals.py
# MM src/billing/totals.py              <- M in both columns: partially staged
git diff --cached -- src/billing/totals.py   # what will be committed
git diff -- src/billing/totals.py            # what stays unstaged
Three versions of a partially staged fileHEAD holds the last committed version. The index holds HEAD plus the staged hunk. The working tree holds the staged hunk plus further unstaged edits. lint-staged must format the index version and leave the working-tree extras intact.the commit is the index, not the working treeWorking treestaged hunk + unstaged editsIndexHEAD + staged hunk — will be committedHEADlast commita formatter that reads the working tree would format edits you did not mean to commit

Step 2 — Understand how lint-staged hides and restores Jump to heading

Before running tasks, lint-staged saves a backup stash of everything, then removes unstaged changes from partially staged files so the working tree matches the index. Tasks run and their fixes are added to the index. Finally it re-applies the unstaged changes on top.

# Watch the steps
npx lint-staged --debug 2>&1 | grep -iE 'stash|hiding|restoring|applying'
lint-staged around a partially staged filelint-staged creates a backup stash, hides the unstaged edits so the file matches the index, runs the formatter, stages its fixes, then re-applies the hidden edits as a patch. If the formatter changed lines the hidden edits also touch, the patch conflicts.lint-stagedgit stashworking treeformatterbackup stash (everything)hide unstaged editsformat index versionfixes stagedre-apply hidden editsthe last step is a patch application — overlapping lines can conflict

The backup stash is the safety net: if anything fails, lint-staged restores from it, and if restoration itself fails, it leaves the stash in place for you.

Step 3 — Recognise a restore conflict Jump to heading

The conflict happens when the formatter rewrites lines that your unstaged edits also change. Typical case: the formatter re-wraps a long function call in the staged hunk, and your unstaged edit added an argument to the same call.

✖ Unstaged changes could not be restored due to a merge conflict!
✖ lint-staged failed due to a git error.
  Any lost modifications can be restored from a git stash:
    > git stash list
    stash@{0}: automatic lint-staged backup
    > git stash apply --index stash@{0}

The commit may or may not have been created, depending on when the error occurred. Check before doing anything else.

git log -1 --format='%h %s'      # did the commit happen?
git stash list | head -3          # is the backup there?

Step 4 — Recover the unstaged work Jump to heading

The backup stash contains the complete state from before lint-staged ran — staged and unstaged. Apply it carefully onto the current state.

# If the commit was created: restore only what was unstaged, on top of the new commit
git stash show -p 'stash@{0}' > /tmp/backup.patch
git diff HEAD~1 HEAD > /tmp/committed.patch          # what actually got committed (formatted)
git stash apply 'stash@{0}'                           # may conflict where formatting changed lines
git status --short

Resolve conflicts by keeping the formatted version of lines that were committed and your edits for the rest, then run the formatter again on the working tree.

⚠️ SAFETY WARNING: Do not run git stash drop or git stash clear until you have confirmed your unstaged work is back. The automatic lint-staged backup stash is the only copy of those edits. If it is dropped by mistake, it can still be found among dangling commits for a while, as described in recovering a dropped stash.

Step 5 — Avoid the conflict in the first place Jump to heading

Three habits make restore conflicts rare: format the whole file before partially staging it, keep unstaged edits away from lines in the staged hunk, and commit in smaller steps.

# Format first, then stage selectively — the formatter has nothing left to change at commit time
npx prettier --write src/billing/totals.ts
git add -p src/billing/totals.ts
git commit
When partial staging is safe with formattersIf the whole file is already formatted, the hook changes nothing and restoration is trivial. If unstaged edits are far from the staged hunk, restoration applies cleanly. If they overlap lines the formatter will change, expect a restore conflict and format first.Where are your unstaged edits?file already formattedSafenothing to rewritefar from staged hunkUsually safepatch appliessame lines as stagedFormat firstor commit togethermost restore conflicts come from a formatter re-wrapping a line you are still editing

Validation checklist Jump to heading

Frequently Asked Questions Jump to heading

Can I turn off the hiding behaviour? Jump to heading

--no-stash disables the backup and the hiding. Then tasks run on the working tree, and fixes can include unstaged changes in the commit. That is acceptable in CI, where nothing is unstaged, and risky locally.

Why does lint-staged sometimes say “Prevented an empty git commit”? Jump to heading

The formatter reverted all staged changes — for example, you staged a formatting-only change that the formatter undid. lint-staged stops the commit rather than create an empty one. Check your formatter configuration matches what you intended.

Does this affect pre-commit (the Python framework) too? Jump to heading

Yes. The pre-commit framework uses the same approach: it stashes unstaged changes as a patch, runs hooks, and restores. Restore conflicts and recovery work the same way.