build: pin every dependency to an exact version, including runtime tool installs - #77
Merged
Conversation
Org-wide policy: no ranges, in any repo, in any ecosystem. A range means the version you get depends on when you install -- CI, a laptop and a release build can each resolve differently -- and a bad upstream release lands without a PR anyone reviewed. Renovate now raises a "Pin dependencies" PR for anything that regresses to a range, and every upgrade arrives as a reviewable PR that CI has to pass. That is the point rather than the cost: the same number of upgrades, each one visible. A survey found the repos were already almost entirely pinned -- Maven and Gradle had no ranges at all, and only four dependencies across two repos needed changing (reqstool-vscode#103, reqstool-client#440). This keeps them that way. Signed-off-by: Jimisola Laursen <jimisola@jimisola.com>
The earlier survey only looked at package manifests, and missed a whole class of dependency: tools installed inside a workflow. They execute in CI and some of them feed a publish, so an unpinned one is exactly the risk pinning exists to remove. Worst of them was `docker run renovate/renovate` with no tag at all -- :latest on every run, so that job's behaviour could change with no commit anywhere. Now 44.32.5. Also pinned: zizmor 1.29.0, yamllint 1.38.0, PyYAML 6.0.3, hatch 1.18.0, twine 7.0.0, @fission-ai/openspec 1.9.0. Each carries a `# renovate:` comment, so they are tracked rather than merely frozen -- the distinction the four earlier pins were about. The custom manager gains one matchString for the inline `tool==1.2.3` and `pkg@1.2.3` forms. The docker image reuses the existing `VERSION="..."` form via a variable, rather than a third pattern: an inline comment there would have needed backticks, which bash would have run as command substitution. install-reqstool stays unpinned on purpose, and now says so. The build matrix runs it with reqstool-source: [pypi, main] precisely to test against whatever reqstool currently releases; pinning it would defeat the check it exists for. Verified all ten annotations are discovered by the manager's regexes. Signed-off-by: Jimisola Laursen <jimisola@jimisola.com>
This was referenced Aug 17, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
ⓘ Corrected after @jimisola asked "are all dependencies in all repos pinned now?" — they were not. My first survey only covered package manifests and missed a whole class.
What & Why
Org-wide policy: no ranges, in any repo, in any ecosystem. A range means the version you get depends on when you install — CI, a laptop and a release build can each resolve differently — and a bad upstream release lands without a PR anyone reviewed.
rangeStrategy: "pin"makes Renovate raise a Pin dependencies PR for anything that regresses. Every upgrade still happens; each is now a reviewable PR that CI must pass.Two classes, not one
Manifests — surveyed first. Already almost entirely pinned:
Runtime tool installs — the class I missed.
pip install <name>/npm install -g <name>/docker run <image>resolve to whatever is newest when the job runs. They execute in CI and some feed a publish.The worst was
docker run renovate/renovatewith no tag at all —:lateston every run, so that job could change behaviour with no commit anywhere.Pinned here:
renovate/renovate44.32.5,zizmor1.29.0,yamllint1.38.0,PyYAML6.0.3,hatch1.18.0,twine7.0.0,@fission-ai/openspec1.9.0. Companion PRs do the same in client#441, decorators#89, hatch-plugin#100, ai#38.Every pin carries a
# renovate:comment, so it is tracked, not merely frozen — the distinction that mattered when Renovate silently skipped the Nisse pin.One deliberate exception
install-reqstoolstays unpinned, and now says why: the build matrix runs it withreqstool-source: [pypi, main]precisely to test against whatever reqstool currently releases. Same for reqstool-regression#35.A near-miss worth recording
My first attempt annotated the docker image with an inline
`# renovate: …`comment. In a shell block backticks are command substitution — bash would have tried to execute the comment. Replaced with aVERSION="44.32.5"variable, which also reuses the existing matchString instead of needing a third pattern.Author checklist
renovate-config-validatorpassesactionlint,zizmor,yamllint, permissions check, action tests clean