What happens
build (3.14, ubuntu-latest, pre-commit) in the Lint workflow failed while installing hook environments:
error: Failed to install hook `hadolint`
caused by: Command `uv pip install --project / --directory /home/runner/.cache/prek/repos/2a6b69af185386d7 .` exited with an error:
× Failed to build `hadolint-py @ ...`
├─▶ The build backend returned an error
╰─▶ Call to `setuptools.build_meta:__legacy__.build_wheel` failed
error: <urlopen error [SSL: CERTIFICATE_VERIFY_FAILED] certificate verify failed: self-signed certificate (_ssl.c:1000)>
https://github.com/VWS-Python/vws-python-mock/actions/runs/31578765061
hadolint-py ships no binary. Its build backend downloads the hadolint release binary during pip install, so installing the hook environment makes a network request to a host which is neither PyPI nor GitHub's API. When that request fails, prek exits 2, and because strategy.fail-fast is not set in .github/workflows/lint.yml it defaults to true, so the other five matrix jobs are cancelled and completion-lint — a required check — fails.
It passed on a re-run.
Why it matters
completion-lint is one of the five checks the require-builds ruleset requires, so a required merge gate depends on an ad-hoc binary download succeeding.
The hook environment is cached, so this only bites on a cache miss — a .pre-commit-config.yaml change, a new runner platform, an evicted cache — which is also exactly when someone is changing linting and least wants an unrelated failure.
This is the same shape as #3402: a required gate whose behaviour depends on something fetched at run time rather than on anything in the repository. Here the fetch is not even version-resolved by a package manager, so there is no lockfile or hash to check it against.
For context on how often the network is the problem rather than the code: on 2026-08-12 a single pull request saw three unrelated TLS failures across two workflows — this one, and actions/checkout failing with server certificate verification failed twice.
Suggested resolution
Options:
- Install
hadolint as a binary in the workflow, with a pinned version and a checksum, and run the hook with language: system. This keeps the pin in the repository and makes the download verifiable, at the cost of a workflow step and a second place to bump the version.
- Run
hadolint through its official Docker image with a pinned digest, using language: docker_image. prek supports this, and it removes the build-time download, but it adds a Docker pull to a lint job which currently needs no Docker.
- Leave it and accept the occasional re-run, but set
fail-fast: false on the Lint matrix so one job's install failure does not cancel five healthy jobs. This is worth doing regardless of which option is chosen — the cancelled jobs are noise which makes the real failure harder to find.
The fail-fast: false part is small and independently useful. The rest is a judgement call about how much a rare, self-clearing failure is worth removing.
What happens
build (3.14, ubuntu-latest, pre-commit)in theLintworkflow failed while installing hook environments:https://github.com/VWS-Python/vws-python-mock/actions/runs/31578765061
hadolint-pyships no binary. Its build backend downloads thehadolintrelease binary duringpip install, so installing the hook environment makes a network request to a host which is neither PyPI nor GitHub's API. When that request fails,prekexits 2, and becausestrategy.fail-fastis not set in.github/workflows/lint.ymlit defaults to true, so the other five matrix jobs are cancelled andcompletion-lint— a required check — fails.It passed on a re-run.
Why it matters
completion-lintis one of the five checks therequire-buildsruleset requires, so a required merge gate depends on an ad-hoc binary download succeeding.The hook environment is cached, so this only bites on a cache miss — a
.pre-commit-config.yamlchange, a new runner platform, an evicted cache — which is also exactly when someone is changing linting and least wants an unrelated failure.This is the same shape as #3402: a required gate whose behaviour depends on something fetched at run time rather than on anything in the repository. Here the fetch is not even version-resolved by a package manager, so there is no lockfile or hash to check it against.
For context on how often the network is the problem rather than the code: on 2026-08-12 a single pull request saw three unrelated TLS failures across two workflows — this one, and
actions/checkoutfailing withserver certificate verification failedtwice.Suggested resolution
Options:
hadolintas a binary in the workflow, with a pinned version and a checksum, and run the hook withlanguage: system. This keeps the pin in the repository and makes the download verifiable, at the cost of a workflow step and a second place to bump the version.hadolintthrough its official Docker image with a pinned digest, usinglanguage: docker_image.preksupports this, and it removes the build-time download, but it adds a Docker pull to a lint job which currently needs no Docker.fail-fast: falseon theLintmatrix so one job's install failure does not cancel five healthy jobs. This is worth doing regardless of which option is chosen — the cancelled jobs are noise which makes the real failure harder to find.The
fail-fast: falsepart is small and independently useful. The rest is a judgement call about how much a rare, self-clearing failure is worth removing.