Skip to content

Rebuild the daily dependency-update branch from main instead of merging into it - #890

Draft
anth-volk wants to merge 4 commits into
pr-description-checkfrom
sim-relock-fix
Draft

anth-volk wants to merge 4 commits into
pr-description-checkfrom
sim-relock-fix

Conversation

@anth-volk

@anth-volk anth-volk commented Oct 21, 2026 •

Copy link
Copy Markdown
Contributor

Fixes #889

Stacked on #862 (base pr-description-check), whose create-PR step names a tracking issue on the first line of the update PR's description; that behaviour is kept.

Problem

The daily Update dependencies workflow has failed on all 24 runs from 2026-09-28 to 2026-10-21; the last successful run was 2026-09-27. Every run fails at "Generate API clients":

error: Failed to parse `uv.lock`
  cause: The entry for package `cryptography` (50.0.1) has wheel `cryptography-49.0.0-cp311-abi3-macosx_11_0_arm64.whl` with inconsistent version (49.0.0)

With #583 open, dependency-update-prepare-branch.sh checked out update-dependencies and ran git merge --no-edit -X theirs origin/main, which merged all seven generated uv.lock files line by line. That mixes entries from two resolutions. Reproduced locally: the same merge of origin/update-dependencies (d8a27a12) with origin/main (07e3ec48) gives the cryptography error above in projects/policyengine-simulation-entry, and Dependency detect-installer has missing source field but has more than one matching package in libs/policyengine-fastapi.

Changes

Following the approach of the Public API's relock (PolicyEngine/policyengine-api#3994):

  • Rebuild from main. The prepare step checks out update-dependencies from origin/main on every run and never merges. It records the commit the remote branch points at (git ls-remote) as the remote_head output. It no longer calls gh, so it no longer gets GH_TOKEN.
  • Replace the branch with a lease. The push step replaces the remote branch with --force-with-lease=refs/heads/update-dependencies:<remote_head>, so a push made to the branch during the run makes this push fail rather than be overwritten. When the branch does not exist, a plain push creates it.
  • Skip pushes that change nothing. The push is skipped when the rebuilt branch has the same files as remote_head; the commit message carries the date, so files are compared, not commits. When the update changes no file on main, nothing is committed or pushed; pushing main's own commit would leave the open PR with no changes.
  • Reuse the open PR. The PR step now runs whenever the update changed files (changes_detected), even when the push was skipped. It reuses an open update PR and replaces its whole description, Fixes #N first, when it differs; before, it changed only the first line. The description says each run replaces the branch, so commits pushed to it by hand are lost on the next run. The open-PR lookup ignores fork PRs whose branch has the same name.

The workflow still runs make update (uv lock --upgrade in every libs/* and projects/* project) and gh pr create / gh pr edit, as #887's sim.dependencies.scheduled-relock check expects. No file that only #887 changes is edited here.

Commits (tests written first)

Tests are in projects/policyengine-simulation-entry/tests/test_workflow_scripts.py, using the fake gh and git of workflow_script_support.FakeCommands.

Commit Before the script change After
Rebuild the dependency-update branch from main on every run 8 failed, 3 passed (of the selected update-dependencies tests; with an open PR the old script fetched update-dependencies and merged origin/main) 34 passed (whole file)
Skip the dependency-update push when the branch already holds the update 5 failed, 33 passed 38 passed
Reuse the open dependency-update PR and replace its description 7 failed, 31 passed 38 passed
Say the dependency-update PR changes only uv.lock files (from self-review: the summary said the workflow "regenerates the API clients", but the clients are not committed; also corrects the PR step's comment) 7 failed, 31 passed 38 passed

Checks

  • ruff 0.15.22 format --check and check (select = ["ALL"]): clean; no new noqa.
  • shellcheck on the three changed scripts: clean.
  • python3 scripts/run_quality_guards.py: passed.
  • python3 -m scripts.rules guard --base-ref origin/pr-description-check: passed.
  • python3 -m scripts.rules render: no changes.
  • Register and check rules for locking, installing and testing from uv.lock #887's sim.dependencies.scheduled-relock check (locked_installs.relock_violations), run on a copy of uv-lock-rules with these four workflow files: no violations; its locked-installs guard passed.

Local run of the job's steps

On a detached origin/main (07e3ec48), with these scripts and a git wrapper that recorded git config and git push instead of running them:

  1. Prepare: recorded remote_head=d8a27a12…; update-dependencies checked out at 07e3ec48, no merge.
  2. ./scripts/generate-clients.sh: succeeded.
  3. make update: succeeded.
  4. uv lock --check in all eight projects: every lock parses and passes.
  5. Commit and push: one commit changing the eight uv.lock files; changes_detected=true, branch_pushed=true; the recorded push was git push --force-with-lease=refs/heads/update-dependencies:d8a27a12c22eb88e5fb7216ab5e37a3746c47239 origin HEAD:refs/heads/update-dependencies.

Nothing was pushed.

Effect on #583 after merge

On the next scheduled run, update-dependencies is replaced by one commit on top of main with that day's lock upgrades, so #583's diff becomes only those upgrades. #583 stays open; its description is replaced with Fixes #N and the new summary, where #N is a new "Update dependencies" issue (none is open now).

Secrets and permissions

  • APP_ID, APP_PRIVATE_KEY: organization Actions secrets (visibility: all repositories), passed to actions/create-github-app-token. The checkout step stores the App token, so git ls-remote, git fetch and git push in the prepare and push steps use it; the PR step gets it as GH_TOKEN.
  • GITHUB_REPOSITORY, GITHUB_OUTPUT: set by the runner for every step.
  • REMOTE_HEAD: the prepare step's remote_head output, passed through the push step's env:.
  • No ruleset or branch protection applies to update-dependencies (GET /repos/{repo}/rules/branches/update-dependencies is empty; the only ruleset, main, targets the default branch), so a force push with a lease is allowed. The App token last pushed to the branch on 2026-09-27 (d8a27a12).

🤖 Generated with Claude Code

anth-volk and others added 3 commits October 22, 2026 01:20
The prepare step merged main into the open update PR's branch with
`-X theirs`. Merging generated uv.lock files line by line mixed two
resolutions, and uv refused the result ("Failed to parse uv.lock"), so
every run since at least 2026-10-10 failed at "Generate API clients".

The prepare step now checks out update-dependencies from origin/main on
every run and records the commit the remote branch points at. The push
step replaces the remote branch with --force-with-lease against that
commit, or creates it with a plain push when it does not exist. Neither
step asks gh about pull requests any more, so the prepare step no longer
gets GH_TOKEN.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The push step now compares the rebuilt branch's files with the commit the
remote branch points at and skips the push when they match; the commit
message carries the date, so the commits themselves always differ. When
the update changes no file on main, nothing is committed or pushed:
pushing main's own commit would leave an open update PR with no changes.

The step reports changes_detected, and the PR step runs whenever the
update changed files on main, so an update PR is opened or kept current
even when the push was skipped.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Each run now rebuilds the update branch from main, so the open update
PR's description said the wrong thing ("Subsequent runs append commits").
The create-PR step still reuses an open update PR, but now replaces its
whole description, "Fixes #N" first, whenever it differs from the
current one; before, it changed only the first line. The description
says that each run replaces the branch, so commits pushed by hand are
lost on the next run.

The open-PR lookup now ignores pull requests from forks whose branch is
also named update-dependencies, as the Public API's relock does, so the
step never rewrites a fork PR's description.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@anth-volk
anth-volk added this pull request to stack #891 October 21, 2026 21:26
The update PR's summary said the workflow "regenerates the API clients",
which reads as if the PR contained client changes. The generated clients
are not committed, so the PR changes only uv.lock files; the summary now
says so. The workflow's comment on the PR step now says the step replaces
an open PR's description, not only its first line.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

This branch has not been deployed

No deployments
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.

Daily dependency update fails: merging main into the update branch produces a uv.lock that uv cannot parse

1 participant