Skip to content

Install CI's Python tools from a hash-pinned lock file - #9

Merged
kkdev92 merged 1 commit into
mainfrom
ci/pin-pip-dependencies
Aug 17, 2026
Merged

Install CI's Python tools from a hash-pinned lock file#9
kkdev92 merged 1 commit into
mainfrom
ci/pin-pip-dependencies

Conversation

@kkdev92

@kkdev92 kkdev92 commented Aug 17, 2026

Copy link
Copy Markdown
Owner

Every CI job installed PlatformIO, and one also gcovr, with a bare
pip install name==version. A version number states which release was asked
for; it does not establish that what arrived is what the maintainer published.
Six of OpenSSF Scorecard's pinned-dependency findings pointed at exactly these
lines.

What changed

  • tools/requirements-ci.in holds the versions, and is the only place they are
    chosen.
  • tools/requirements-ci.txt is generated from it with the full transitive
    closure pinned by hash — 28 packages, 445 hashes.
  • Every job installs with pip install --require-hashes -r tools/requirements-ci.txt, so a package whose contents do not match its
    recorded hash fails the install instead of running.
  • The PLATFORMIO_VERSION and GCOVR_VERSION environment variables are gone.
    Cache keys now hash the lock file, so the cache follows the dependency set
    instead of relying on someone remembering to bump a key.

Resolve the lock for the runner, not for your laptop

Dependencies are conditional on both platform and Python version, and
--require-hashes fails the entire install if one is missing rather than
fetching it. Resolving this set on Windows with Python 3.13 omits
typing-extensions, which anyio requires on Python 3.12 — the CI runners
would then refuse to install anything.

The regeneration command in the header of requirements-ci.in pins both the
version and the platform for that reason:

uv pip compile --generate-hashes --python-version 3.12 --python-platform linux \
  --output-file tools/requirements-ci.txt tools/requirements-ci.in

Verification

The install was resolved and hash-checked for linux / Python 3.12, matching
the runners, before this was pushed. CI on this pull request exercises all six
install steps for real.

🤖 Generated with Claude Code

Every CI job installed PlatformIO, and one also gcovr, with a bare
`pip install name==version`. A version number says which release was asked
for; it does not say that what arrived is what the maintainer published. Six
of Scorecard's pinned-dependency findings pointed at exactly these lines.

tools/requirements-ci.in now holds the versions, and tools/requirements-ci.txt
is generated from it with the full transitive closure pinned by hash: 28
packages, 445 hashes. Every job installs with --require-hashes, so a package
that does not match fails the install rather than running.

The lock has to be resolved for the runner rather than for a maintainer's
machine, because dependencies are conditional on both platform and Python
version. Resolving it on Windows with Python 3.13 silently omits
typing-extensions, which anyio requires on 3.12 and CI would then refuse to
install under --require-hashes. The regeneration command in the .in header
pins both, and the reason is written down next to it.

The PlatformIO and gcovr version environment variables are gone; the lock file
is the single place a version is chosen. Cache keys now hash the lock instead
of interpolating those variables, so the cache follows the dependency set
without anyone having to remember to bump a key.

Verified by resolving and hash-checking the install for linux / Python 3.12,
which is what the runners use.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@kkdev92
kkdev92 merged commit 11504a4 into main Aug 17, 2026
13 checks passed
@kkdev92
kkdev92 deleted the ci/pin-pip-dependencies branch August 17, 2026 13:27
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.

1 participant