Skip to content

Harden CI: the pip bootstrap step installs from a hash-pinned set - #5929

Merged
vivekchand merged 1 commit into
mainfrom
harden/pin-pip-bootstrap-installs
Sep 13, 2026
Merged

Harden CI: the pip bootstrap step installs from a hash-pinned set#5929
vivekchand merged 1 commit into
mainfrom
harden/pin-pip-bootstrap-installs

Conversation

@vivekchand

Copy link
Copy Markdown
Owner

Product record: No-PRD: CI-only change under .github/; no product behaviour is touched.

Risk: Low, and contained to CI. If a hash or version in either new file is wrong, the affected job fails loudly at its first step with a pip hash-mismatch error — there is no silent-wrong mode, and nothing is published or deployed from these five workflows. Undone by reverting the commit. Nothing in flight collides: no other open PR touches .github/requirements/. The one behaviour change to reason about is that pip is now installed at an exact version rather than "latest", which is the point of the change.


Summary

The two files

File Contents Sites
.github/requirements/pip-bootstrap.txt pip 6
.github/requirements/pip-build-bootstrap.txt pip, build, packaging, pyproject_hooks 3

Deliberately two files rather than one. Six of the nine sites do not build anything, and three of those bootstrap a throwaway venv that the E2E suites then use to prove a freshly installed wheel boots on its own. Adding build and its closure to those venvs would widen the dependency surface the test is measuring — which is exactly the bug an isolated-venv test exists to catch. As it stands, every job's installed set is byte-for-byte what it was before this PR, with the versions now fixed.

Both files join the existing /.github/requirements Dependabot entry, so the pins move rather than freeze — the same pin/updater pairing the sibling files document.

Scope, and what is deliberately left out

This PR touches only the PR-triggered workflows, so its own CI exercises all nine hunks. The remaining seven bootstrap sites are left for separate changes, for reasons that are not "ran out of time":

  • conformance-heartbeat.yml (2), desktop-artifacts.yml (2), release-canary.yml (1), release-on-merge.yml (1) — none are pull_request-triggered, so no PR can validate a change to them before it lands on main. Pinning the release pipeline's bootstrap on a run that cannot test it is how you find out at release time.
  • pr-screenshots.yml (1) — it checks out into head/ and base/, so there is no .github/ at the workspace root and the reference has to name one of the two trees. That is a trust decision (head/ is PR-author-controlled), not a path fix, and it deserves its own review. It would also fail on this PR, since base is main and would not yet contain the file.

Test plan

  • Every workflow file parses: python3 -c "import yaml,glob; [yaml.safe_load(open(f)) for f in glob.glob('.github/workflows/*.yml')]" — 40/40 OK
  • Both sets install under --require-hashes in a clean 3.11 venv, and pip ends at 26.2.1 in each — i.e. the replacement does what --upgrade pip did
  • python -m build --wheel with the pinned build toolchain produces clawmetry-*-py3-none-any.whl, so the three build sites still build
  • Simulated the cross-repo-handoff install order (pip-bootstrap.txt then cross-repo-handoff.txt) in one venv — no version conflict; both pin packaging==26.3 identically
  • tests/test_workflow_yaml_valid.py, tests/test_action_refs_pinned.py, tests/test_ci_workflow_invocations_are_real.py — 613 passed, 370 skipped
  • make lint-ci-test-coverage, lint-runtime-count, lint-module-map, lint-daemon-allowlist — all green
  • make lint-py fails identically (231 errors, all in dashboard.py) on this branch and on a clean main, so it is pre-existing and unrelated; this diff contains no Python

Hashes were generated by the procedure the sibling requirements files document: pip install --dry-run --ignore-installed --report on Linux / CPython 3.11 to match the jobs, then every sha256 under urls in the PyPI JSON for each resolved version.

🤖 Generated with Claude Code

https://claude.ai/code/session_011qRmtoZTzuS7CxzZ9GnsU9


Generated by Claude Code

The jobs that already install everything else with --require-hashes still
opened with a bare `python -m pip install --upgrade pip`. That resolve was
unpinned, so the job fetched whatever pip PyPI served that minute and then
used THAT pip to verify every hash below it. A hash-checked install is only
worth as much as the installer doing the checking, so this was the one
remaining gap in those jobs' supply chain.

Nine call sites across five workflows now install from one of two new
hash-pinned sets:

  .github/requirements/pip-bootstrap.txt        pip
  .github/requirements/pip-build-bootstrap.txt  pip + build (+ packaging,
                                                pyproject_hooks)

Deliberately two files. Seven of the ten bootstrap sites in the repo do not
build anything, and three of those bootstrap a throwaway venv that the E2E
suites then use to prove a freshly installed wheel boots on its own. Adding
`build` and its closure to those venvs would widen the dependency surface
the test is measuring, which is exactly the bug an isolated-venv test exists
to catch. Every job's installed set is identical to what it was before.

`--upgrade` is gone because naming an exact version does the same job: pip
26.2.1 is installed over whatever actions/setup-python shipped, which is
what the upgrade was reaching for. Both files join the /.github/requirements
Dependabot entry, so the pins move rather than freeze.

Scope: only the five PR-triggered workflows, so this PR's own CI exercises
every one of the nine hunks. The remaining bootstrap sites are deliberately
left for a separate change -- see the PR body.

No-PRD: CI-only change under .github/; no product behaviour is touched.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011qRmtoZTzuS7CxzZ9GnsU9
@8090-software-factory

Copy link
Copy Markdown

✅ Drift Bot (ClawMetry): no drift detected

Drift Bot analyzed the changed files against this project's blueprints and requirements and found no drift.

@vivekchand
vivekchand merged commit 911b84a into main Sep 13, 2026
42 checks passed
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.

2 participants