Repository navigation
Skip superseded Push versioning instead of failing, and bound every Push job with a timeout - #10016
Merged
Merged
Conversation
When several PRs merge before the versioning job gets a runner, every job but the newest commits a version bump on a stale parent and fails on push. Oct 6 00:00Z to Oct 8 16:28Z, 47 of 51 failed Push runs were exactly this: in all 47, main had already moved past the job's commit before the job started, and in every successful versioning run it had not. The newest merge's bump consumes all pending changelog fragments (for example, 7c857cf removed the three fragments from the 13:02Z burst on Oct 8), so the older jobs had nothing to do. The job now reads main's head first. If main has moved past github.sha it records a notice and skips the remaining steps, so the run succeeds and "Push" failures on main again mean something. If the lookup fails, the job proceeds as before. The job is not cancelled and keeps no concurrency group. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Five Push jobs had no timeout-minutes and so inherited GitHub's 360-minute default. Each now has a limit well above its Oct 6-8 maximum: ReleaseLock, ReleaseLockTests and Lint 10 min (max 0.4, 1.4 and 0.7), versioning 20 (max 12) and Publish 20 (max 1.3). A ReleaseLockTests run that hung for 45 min (37650158537) would have stopped at 10. The push Rest job's limit rises from 60 to 90 minutes, matching the PR Rest job after #9785. On push its p50 was 51 min and its successful max 58.8, and run 37719726248 hit the 60-minute limit, which skipped that version's Publish. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The skip compared main's head SHA with this commit for equality, so any mismatch skipped, including a stale read that returned an older head. If the newest merge's job got such a read, every job would skip and no version bump would land. The step now asks GitHub's compare API for the status of this commit against main and skips only on "ahead" (this commit is an ancestor of main and main has more). Identical, behind, diverged, an empty answer or a failed lookup all run versioning as before. A new code-health test runs the step's script against a stub gh for every compare status and for a failed lookup, and checks that every later step in the job is gated on the result. Replacing the condition with a looser one fails five of its nine cases. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Contributor
Author
|
Hand-off to the US + core hub (from session local_cfdb0a5b, the PolicyEngine Actions concurrency chip).
|
Contributor
Author
|
US + core hub merge audit, us#10016 at
|
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.
Summary
Two changes to
.github/workflows/push.yaml, plus a changelog fragment:This is part of an org-wide GitHub Actions capacity investigation. The org's 60 standard-runner slots were saturated on Oct 6 and Oct 7, with demand of about 84k runner-min/day against 86.4k of capacity.
Data and method
filter=all).completed_at − started_at, counted only for jobs that recorded arunner_name.failure.created_atmarks a push to main.1. Versioning skips instead of failing
What happens today
On a PR merge, Push runs
Release lock guard testsand thenUpdate versioning. Versioning checks outgithub.sha, bumps the version, runs towncrier, and pushes anUpdate PolicyEngine UScommit (EndBug/add-and-commit,fetch: false).Both jobs wait for a runner. On merge pushes in the window, with the wait measured per job as
started_at − created_at:When several PRs merge inside that window, every versioning job except the newest commits on a stale parent. Its push is then rejected. The log of job 113391313632 shows
! refs/heads/main:refs/heads/main [rejected] (non-fast-forward)at the add-and-commit step.Measured
Why nothing is lost
towncrier builds all of
changelog.d, andbump_version.pyinfers the bump from every fragment. So the newest merge's bump consumes every pending fragment. For example, 7c857cf removed the three fragments from the burst of three merges at 13:02Z on Oct 8. The 47 failures were false alarms, and they hide the 4 real failures.The fix
A new first step asks GitHub's compare API how main relates to this commit:
gh api repos/$GITHUB_REPOSITORY/compare/$GITHUB_SHA...main --jq .status, using the job'sGITHUB_TOKEN.partner-api-impact-notice.yamlalready usesgh apionubuntu-latest.ahead(this commit is an ancestor of main, and main has more). The step writes a::noticetitled "Versioning superseded". Every later step (all 9) is gated onsteps.head.outputs.current == 'true', so they skip. The job succeeds in seconds and skips the full-history checkout.identical,behind,diverged, empty) or a failed lookup. The job runs exactly as before; a failed lookup also writes a::warning.The first version compared main's head SHA with
github.shafor equality. An independent review showed that a stale read returning an older head would then skip the newest merge's job too, leaving no bump at all. Skipping only onaheadcloses that: the newest merge's job skips only if GitHub reports a newer descendant of its commit on main, so a stale read cannot make it skip falsely (assuming main only moves forward).The job has no concurrency group, so concurrency never cancels it.
2. Timeouts
The table below uses Push job durations from the window. "n" counts jobs that completed with
successorfailure.Expected runner-min/day saved
fetch-depth: 0checkout, which took 3.6 of 3.7 min in job 113391313632.Invariants
github.shaand pushes to main without fetching. A later job (main's newest merge) is responsible for the bump. Any doubt (a failed, empty or stale answer) runs the job as today.changelog.d. The newer push's versioning job, or the next merge's, consumes them.Risks and how they are bounded
behindoridentical, so the job runs and behaves as today.skip-existing: true.GITHUB_TOKEN; it needscontents: read, which the workflow inherits (it declares nopermissions). It uses no new secret.Runner budget
CLAUDE.md's "Test design and CI cost" section governs runner-budget changes. This PR adds no jobs, matrix entries, runners or concurrent processes. It adds one step (one API call) to an existing job. It lowers or caps job durations, except the push Rest limit, which now matches the PR Rest job.
Verification
Run on this branch after it was rebased onto the latest
origin/main(e44d7c3), in a fresh worktree withuv sync --locked --extra devon Python 3.14:actionlint .github/workflows/push.yaml(actionlint 1.7.12)RELEASE_LOCK_REAL_UV=1 python -m unittest discover -s .github/tests -p 'test_release_*.py'(the PR validation step; it includestest_release_build.py, which parses the Publish job)uv run pytest policyengine_us/tests/code_health/test_rest_python_groups.py policyengine_us/tests/code_health/test_make_test_matches_ci.py policyengine_us/tests/code_health/test_pr_runner_budget.py(the code-health tests that parse the workflows)uv run ruff format --check .(ruff 0.15.5)A property check on the parsed push.yaml also passes:
bash -ewith a stubghfor three cases (first version; superseded by the committed test below).origin/main's push.yaml.Second round (commit 59d4fbd): a new committed test,
policyengine_us/tests/code_health/test_push_versioning_superseded.py, runs the step's script with a stubghfor every compare status (ahead,identical,behind,diverged, empty,null) and a failed lookup, checks that the compare goes from this commit tomain, and checks that every later step is gated. 9 passed. Mutation check: loosening the condition to "anything but identical" fails 5 of the 9.actionlint .github/workflows/push.yamlis clean on base and head. The branch also merges currentorigin/main(f3456c7).actionlinton the whole repo reports only existing shellcheck style notes inpr.yamlandweekly-uv-lock.yaml, which this PR does not touch.After merge, check: the next burst of merges should show green versioning jobs with a "Versioning superseded" notice. A burst usually gets one
Update PolicyEngine UScommit; it gets two when one merge's bump lands before the next merge.Known wording limit (left as is to keep the reviewed head): the skip notice says the newest merge's versioning job bumps the version with this commit's fragments. When main is ahead only because that bump already landed, the fragments were already consumed.
Related
#10017, a draft stacked on this branch, holds the per-shard concurrency change from the same investigation. It needs a release-policy decision, because superseded versions would not be published to PyPI. This PR does not depend on it.
axiom: n/a: CI infrastructure change
🤖 Generated with Claude Code