Skip to content

ci(pre-commit): fail the job when hooks never ran - #14754

Open
TSC21 wants to merge 1 commit into
mavlink:masterfrom
TSC21:ci/pre-commit-fail-when-hooks-never-ran
Open

ci(pre-commit): fail the job when hooks never ran#14754
TSC21 wants to merge 1 commit into
mavlink:masterfrom
TSC21:ci/pre-commit-fail-when-hooks-never-ran

Conversation

@TSC21

@TSC21 TSC21 commented Jul 31, 2026

Copy link
Copy Markdown
Member

RIIS

Contributed on behalf of RIIS, LLC.



CI/Build Changes

The pre-commit job's crash guard now fails the job on any pre-commit completion other than exit 0 (all hooks passed) or exit 1 (hooks ran and some failed). It keeps the current report-only design for real hook findings: those still land in the PR comment and do not red the job.

Reason

The current guard only fails the job when the runner script dies without writing an exit code at all. pre-commit has a second failure shape the guard misses: it can abort before running any hook and still exit with a code, for example when a hook environment fails to install (exit code 3). In that shape the step writes exit_code=3 with zero hook verdicts, the guard sees a non-empty code, and the check lands green while every hook in the config was skipped. That is exactly what has been happening: see the companion PR fixing the vale-sync install, which was silently killing every pre-commit run. A gate that can pass without running is not a gate, so this closes the detection side while the companion PR fixes the current trigger.

Testing

  • Tested workflow locally (act or similar)
  • Verified YAML syntax (actionlint, zizmor and yamllint pass on the workflow)
  • Tested on fork before submitting (the failing shape is reproduced on my fork: a run with exit_code: 3, passed: 0, failed: 0 in the results artifact and a green check; with this guard that run fails)

Impact

Only the pre-commit workflow. Hook findings keep their report-only behaviour; the job only goes red when pre-commit never produced verdicts.

Checklist

  • I have read the Contribution Guidelines
  • Workflow permissions follow least-privilege principle (permissions unchanged)
  • No secrets are exposed in logs

By submitting this pull request, I confirm that my contribution is made under the terms of the project's dual license (Apache 2.0 and GPL v3).

The job treats hook failures as report-only (the PR comment carries
them) and is meant to go red only when the runner itself breaks. The
crash guard tests for one shape of that break: the script dying
before it writes any exit code. pre-commit has a second shape: it
aborts before running hooks and still exits with a code (environment
install failures exit 3), so the step writes exit_code=3 with zero
hook verdicts, the guard sees a non-empty code, and the check lands
green while every hook in the config is skipped.

The guard fails the job on any completion other than clean (0) or
hooks-ran-and-some-failed (1), which keeps the report-only design
for real hook findings and reds the job for every shape of
never-ran.
@TSC21
TSC21 requested a review from HTRamsey as a code owner July 31, 2026 17:57
@github-actions github-actions Bot added github_actions Pull requests that update GitHub Actions code size/XS labels Jul 31, 2026
@github-actions

github-actions Bot commented Jul 31, 2026

Copy link
Copy Markdown
Contributor

⚠️ Build results unavailable — artifact download from one or more platform workflows failed (likely artifact retention expiry or transient API error). The combined report cannot be generated for this run.

See the Build Results workflow run for details.

@TSC21

TSC21 commented Jul 31, 2026

Copy link
Copy Markdown
Member Author

The red pre-commit check on this PR is this PR's own guard working: pre-commit on master currently aborts with exit code 3 before running a single hook (the vale-sync environment install failure fixed in #14753), and with this guard that state fails the job instead of landing green with zero hook verdicts.

So the two PRs go together: #14753 fixes the breakage, this one makes sure the next breakage of this class is visible. Merging #14753 first turns this one green.

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Updates the pre-commit workflow guard so infrastructure failures cannot silently skip all hooks while CI remains green.

Changes:

  • Preserves report-only handling for normal hook failures (exit_code=1).
  • Fails the job for missing or unexpected exit codes.
  • Improves failure diagnostics.

💡 Add a code-review agent skill for context-aware, tailored reviews. Learn more in the docs.

@DonLakeFlyer DonLakeFlyer added this to the Release V5.1 milestone Aug 8, 2026
Comment on lines +91 to 102
# Hook failures are surfaced via the PR comment; the job itself fails only when the runner
# never produced hook verdicts. That is two shapes: the script died before writing results
# (no exit_code output), or pre-commit aborted before running hooks (exit codes other than
# 0/1, e.g. a hook environment that fails to install). Treating the second shape as green
# leaves every hook silently skipped while the check shows passing.
- name: Fail job on pre-commit script crash
if: always() && steps.pre-commit.outcome == 'failure' && steps.pre-commit.outputs.exit_code == ''
if: always() && steps.pre-commit.outcome == 'failure' && steps.pre-commit.outputs.exit_code != '1'
env:
EXIT_CODE: ${{ steps.pre-commit.outputs.exit_code }}
run: |
echo "::error::pre-commit runner crashed before writing results"
echo "::error::pre-commit runner crashed before running hooks (exit_code='${EXIT_CODE}')"
exit 1

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There is a third shape that still lands green: pre-commit exits 1 for fatal errors too, not just hook failures — e.g. a malformed .pre-commit-config.yaml raises FatalError, which exits 1 with zero hooks run. That produces exit_code=1, passed=0, failed=0 and passes this guard. Since the runner already writes passed/failed outputs, the guard can close that shape as well:

Suggested change
# Hook failures are surfaced via the PR comment; the job itself fails only when the runner
# never produced hook verdicts. That is two shapes: the script died before writing results
# (no exit_code output), or pre-commit aborted before running hooks (exit codes other than
# 0/1, e.g. a hook environment that fails to install). Treating the second shape as green
# leaves every hook silently skipped while the check shows passing.
- name: Fail job on pre-commit script crash
if: always() && steps.pre-commit.outcome == 'failure' && steps.pre-commit.outputs.exit_code == ''
if: always() && steps.pre-commit.outcome == 'failure' && steps.pre-commit.outputs.exit_code != '1'
env:
EXIT_CODE: ${{ steps.pre-commit.outputs.exit_code }}
run: |
echo "::error::pre-commit runner crashed before writing results"
echo "::error::pre-commit runner crashed before running hooks (exit_code='${EXIT_CODE}')"
exit 1
# Hook failures are surfaced via the PR comment; the job itself fails only when the runner
# never produced hook verdicts. That is three shapes: the script died before writing results
# (no exit_code output), pre-commit aborted before running hooks (exit codes other than
# 0/1, e.g. a hook environment that fails to install), or pre-commit exited 1 without any
# hook verdicts (e.g. a fatal config error). Treating those as green leaves every hook
# silently skipped while the check shows passing.
- name: Fail job on pre-commit script crash
if: >-
always() && steps.pre-commit.outcome == 'failure' &&
(steps.pre-commit.outputs.exit_code != '1' ||
(steps.pre-commit.outputs.passed == '0' && steps.pre-commit.outputs.failed == '0'))
env:
EXIT_CODE: ${{ steps.pre-commit.outputs.exit_code }}
PASSED: ${{ steps.pre-commit.outputs.passed }}
FAILED: ${{ steps.pre-commit.outputs.failed }}
run: |
echo "::error::pre-commit produced no hook verdicts (exit_code='${EXIT_CODE}' passed='${PASSED}' failed='${FAILED}')"
exit 1

@DonLakeFlyer

Copy link
Copy Markdown
Contributor

@TSC21 Can you look at the review?

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

Labels

github_actions Pull requests that update GitHub Actions code size/XS

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants