Skip to content

fix: fail the hook when staging fixed files errors - #1484

Merged
mrexox merged 2 commits into
evilmartians:masterfrom
teddytennant:fix/fail-on-stage-fixed-error
Aug 28, 2026
Merged

mrexox merged 2 commits into
evilmartians:masterfrom
teddytennant:fix/fail-on-stage-fixed-error

Conversation

@teddytennant

@teddytennant teddytennant commented Aug 8, 2026 •

Copy link
Copy Markdown
Contributor

Closes #1471

Context

With stage_fixed: true, guard.withHiddenUnstagedChanges calls git.AddFiles to stage the files a job has fixed. When that git add fails, the error is only logged:

if err := g.git.AddFiles(g.filesToStage.Files()); err != nil {
    g.logger.Warn("Couldn't stage fixed files:", err)
}

return resErr

resErr is the wrapped result of the jobs, so the staging failure never reaches the caller. Lefthook.run sees err == nil, prints a green summary and exits 0 β€” the commit is created with the pre-fix content while nothing in the exit status tells the wrapper or CI that the formatter gate did not actually do its job.

Reproducer from the issue: run the hook without GIT_INDEX_FILE during git commit -a, so the staging git add collides with the index.lock git holds:

> git add --force -- src/example.tsx
fatal: Unable to create '.../.git/index.lock': File exists.
Couldn't stage fixed files: error in batch 0: exit status 128
summary: (done in 2.75 seconds)
βœ”οΈ lint-staged (2.75 seconds)

Changes

errors.Join the AddFiles error into the returned error at both call sites β€” the len(partiallyStagedFiles) == 0 early return and the CanRestoreUnstagedChanges() branch. The warning is kept as is, so the immediate output does not change; only the returned error does. Lefthook.run then reports failed to run the hook: ... and exits non-zero.

AddFiles returns nil for an empty file list, so hooks that stage nothing are unaffected β€” the join only happens on a real error.

Added Test_guard_wrap_stageFixed to guard_test.go covering both call sites, plus the two non-failing paths (nothing to stage, and a successful git add) so the change cannot start failing hooks that used to pass. cmdtest.Out grew an Err field so the ordered command double can simulate a failing git call; it is a new field with a nil zero value, existing expectations behave exactly as before.

Verification

Before the fix (test only, guard.go reverted):

--- FAIL: Test_guard_wrap_stageFixed (0.00s)
    --- FAIL: Test_guard_wrap_stageFixed/staging_fixed_files_fails (0.00s)
        	Error:      	Expected error with "exit status 128" in chain but got nil.
    --- FAIL: Test_guard_wrap_stageFixed/staging_fixed_files_fails_with_partially_staged (0.00s)
        	Error:      	Expected error with "exit status 128" in chain but got nil.
    --- PASS: Test_guard_wrap_stageFixed/no_files_to_stage (0.00s)
    --- PASS: Test_guard_wrap_stageFixed/fixed_files_staged (0.00s)

After:

--- PASS: Test_guard_wrap_stageFixed (0.00s)
    --- PASS: Test_guard_wrap_stageFixed/no_files_to_stage (0.00s)
    --- PASS: Test_guard_wrap_stageFixed/fixed_files_staged (0.00s)
    --- PASS: Test_guard_wrap_stageFixed/staging_fixed_files_fails (0.00s)
    --- PASS: Test_guard_wrap_stageFixed/staging_fixed_files_fails_with_partially_staged (0.00s)

make test and make lint (golangci-lint 2.11.4, 0 issues) pass. make test-integration passes 57/57; tests/integration/run_interrupt.txt is flaky on my machine but fails the same way on an unmodified master, so it is unrelated.

Not changed

  • No new config option. The issue offered "fail the hook, or make it configurable"; failing looks like the right default since the alternative is silently committing unfixed content, and stage_fixed already fails the hook for every other error in this path. Happy to put it behind a flag instead if you prefer to keep the old behaviour available.
  • The else branch (CanRestoreUnstagedChanges() false) is untouched: it already overwrites wrappedErr with errRestorationConflict, so those runs fail regardless.
  • No integration test β€” making git add fail deterministically inside testscript needs an index lock dance that would be more fragile than what it verifies.

Confidence Score: 5/5

The PR appears safe to merge.

No blocking failure remains.

Reviews (2): Last reviewed commit: "fix: add context to the staging error" | Re-trigger Greptile

When stage_fixed is enabled and the git add that stages the fixed files
fails, the error was only logged: the hook still reported success and
exited 0, so the commit went through with the unfixed content.

Join the AddFiles error into the result at both call sites so the run is
marked as a failure.
@teddytennant
teddytennant requested a review from mrexox as a code owner August 8, 2026 19:50
Comment thread internal/run/controller/guard.go Resolved
AddFiles returns the bare git error, so a joined hook failure gave no clue
which operation broke when the warning output isn't visible.
@teddytennant

Copy link
Copy Markdown
Contributor Author

Anything I can do to help get this reviewed?

@mrexox

mrexox commented Aug 28, 2026

Copy link
Copy Markdown
Member

Hey @teddytennant! Sorry for a long delay. I'm going to merge the changes, thank you for preparing the PR!

@mrexox
mrexox merged commit 2f0a9f3 into evilmartians:master Aug 28, 2026
15 checks passed
eleboucher pushed a commit to eleboucher/memini that referenced this pull request Aug 31, 2026
This PR contains the following updates:

| Package | Type | Update | Change |
|---|---|---|---|
| [lefthook](https://github.com/evilmartians/lefthook) | tools | patch | `2.1.11` β†’ `2.1.12` |

---

> ⚠️ **Warning**
>
> Some dependencies could not be looked up. Check the [Dependency Dashboard](issues/3) for more information.

---

### Release Notes

<details>
<summary>evilmartians/lefthook (lefthook)</summary>

### [`v2.1.12`](https://github.com/evilmartians/lefthook/blob/HEAD/CHANGELOG.md#2112-2026-08-28)

[Compare Source](evilmartians/lefthook@v2.1.11...v2.1.12)

- fix: fail the hook when staging fixed files errors ([#&#8203;1484](evilmartians/lefthook#1484)) by [@&#8203;teddytennant](https://github.com/teddytennant)
- fix: LEFTHOOK\_OUTPUT precedence ([#&#8203;1506](evilmartians/lefthook#1506)) by [@&#8203;Yuki9814](https://github.com/Yuki9814)
- ci: fix npm publishing by bumping Node to 24 ([#&#8203;1508](evilmartians/lefthook#1508)) by [@&#8203;mariokresic](https://github.com/mariokresic)

</details>

---

### Configuration

πŸ“… **Schedule**: (UTC)

- Branch creation
  - Between 12:00 AM and 03:59 AM, only on Monday (`* 0-3 * * 1`)
- Automerge
  - At any time (no schedule defined)

🚦 **Automerge**: Disabled because a matching PR was automerged previously.

β™» **Rebasing**: Whenever PR becomes conflicted, or you tick the rebase/retry checkbox.

πŸ”• **Ignore**: Close this PR and you won't be reminded about this update again.

---

 - [ ] <!-- rebase-check -->If you want to rebase/retry this PR, check this box

---

This PR has been generated by [Mend Renovate CLI](https://github.com/renovatebot/renovate).
<!--renovate-debug:eyJjcmVhdGVkSW5WZXIiOiI0NC41Mi4wIiwidXBkYXRlZEluVmVyIjoiNDQuNTIuMCIsInRhcmdldEJyYW5jaCI6Im1haW4iLCJsYWJlbHMiOlsiZGVwZW5kZW5jaWVzIl19-->

Reviewed-on: https://git.erwanleboucher.dev/eleboucher/memini/pulls/242
@aminya

aminya commented Sep 30, 2026 •

Copy link
Copy Markdown
Contributor

I was on 2.1.10, and I noticed the changes suddenly got disappeared from history altogether when the hook failed and had to use reflog for an emergency restore. Does this PR address that issue potentially? @teddytennant

@teddytennant

teddytennant commented Sep 30, 2026 •

Copy link
Copy Markdown
Contributor Author

No. This is not in 2.1.10, and a failing hook does not move HEAD. The only reflog entry lefthook writes is the lefthook auto backup stash, and this PR does not touch that.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

stage_fixed: failed staging (git add) is logged but the command still reports success and the hook exits 0

3 participants