Skip to content

build: pin Python deps with uv.lock for reproducible releases - #644

Open
fwh888 wants to merge 1 commit into
andrewyng:mainfrom
fwh888:fix/python-lockfile-reproducible-builds
Open

build: pin Python deps with uv.lock for reproducible releases#644
fwh888 wants to merge 1 commit into
andrewyng:mainfrom
fwh888:fix/python-lockfile-reproducible-builds

Conversation

@fwh888

@fwh888 fwh888 commented Sep 6, 2026

Copy link
Copy Markdown

Fixes #517.

The Python side of the project had no lockfile: every install (dev venvs, CI, and the desktop release builds) resolved dependencies fresh from PyPI at build time. Two DMG/MSI builds on different days could ship different dependency versions, with no record of what actually shipped — making vulnerability triage of released artifacts guesswork.

What this does

  • Adds uv.lock (102 packages, core + all extras) committed to the repo, so every install path resolves the exact same dependency set.
  • Adds a build extra (pyinstaller + typer) so the build-time-only deps are pinned too, instead of being installed ad-hoc in the build scripts.
  • Switches every install path to uv sync --frozen:
    • packaging/setup_dev_env.sh (dev bootstrap)
    • packaging/build_dmg.sh / packaging/build_windows.ps1 (desktop builds)
    • .github/workflows/ci.yml (pytest job)
    • .github/workflows/release.yml (sidecar venv provisioning)

Why uv

The other three trees in this repo already do this right: surfaces/gui/package-lock.json, stt/Cargo.lock, surfaces/gui/src-tauri/Cargo.lock are all committed. Python was the one gap. uv is the fastest resolver and its lockfile is a single committed file with hashes for every package.

Verification

  • uv sync --frozen installs cleanly from the lockfile.
  • pytest suite passes: 1946 passed, 1 skipped (the 8 bedrock failures are local-only — boto3 not installed; CI installs the bedrock extra).
  • uv lock --check passes against the committed lockfile.

Follow-up (not in this PR)

A CI step that scans the lockfile (e.g. osv-scanner -L uv.lock) would catch dependency regressions at PR time — happy to add it in a follow-up if maintainers want it.

The Python side of the project had no lockfile: every install (dev venvs,
CI, and the desktop release builds) resolved dependencies fresh from PyPI
at build time. Two DMG/MSI builds on different days could ship different
dependency versions, with no record of what actually shipped - making
vulnerability triage of released artifacts guesswork.

- Add uv.lock (102 packages, core + all extras) committed to the repo.
- Add a 'build' extra (pyinstaller + typer) so the build-time-only deps
  are pinned too, instead of being installed ad-hoc in the build scripts.
- Switch setup_dev_env.sh, build_dmg.sh, build_windows.ps1, CI and the
  release workflow to 'uv sync --frozen' so every install path resolves
  the exact pinned set.

Verified: uv sync --frozen installs cleanly and the pytest suite passes
(1946 passed; the 8 bedrock failures are local-only, boto3 not installed).
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.

Supply chain: no Python lockfile — release builds resolve dependencies fresh from PyPI, making shipped versions unreproducible and unscannable

1 participant