fix(ci): resolve perf baseline to latest published master release, and let a manual run choose both sides - #311
Open
raphaelvigee wants to merge 2 commits into
Open
Conversation
The baseline was the previous commit (`github.event.before` on push, the exact merge-base on PRs) and its release tag was re-derived from that one commit's CI run. Both assumptions fail routinely: - CI runs once per push, on that push's head commit, so every other commit in a batched push has no run and no release at all. `59585cc6` is on master with zero CI runs. - master builds run concurrently (per-sha concurrency groups), so the previous commit's release may not be published yet when this job runs — or ever, if its build broke. Either case failed the job outright with "no baseline found", which is how this gate has spent most of its life reporting a data-availability problem instead of comparing anything. Resolve the baseline to the latest *published* master release at/before the baseline point instead: walk master-push CI runs newest-first, keep the first ancestor whose re-derived tag exists as a release. PRs still try the exact merge-base first and only walk when it has none, reporting a note when they do, since a walked baseline widens what the gate attributes to the PR. Also add `workflow_dispatch` with an optional `baseline` input — a commit SHA or an exact release tag, empty for the automatic resolution above. The candidate is the dispatched commit's own published release, so a manual run builds nothing; master commits only, since a PR run's tag derives from its ephemeral merge commit and cannot be re-derived from the branch head the runs API exposes. Supporting fixes found while testing this end to end against the live API: - A non-404 from the artifacts repo (expired PAT, outage) no longer reads as "release does not exist" — that turned a dead token into a confident "no published master release found" pointing at the wrong system. - The run listing is captured rather than process-substituted, so a failed `gh api` gets its own message instead of an empty read. - Both fetch steps verify assets by name: `gh release download` exits 0 when only some of its patterns matched, so a partially published release died on a bare `cp: cannot stat` instead of degrading or naming what was missing. - Run lookups use the workflow-scoped endpoint, dropping the `.name=="CI"` string coupling and no longer sharing the 50-run window with the other push-triggered workflows. - Tier A is gated on the Tier B assets too, which it needs anyway. - The report, the PostHog event, and the filed issue all carry the resolved baseline version and how it was chosen; verdict JSON is uploaded as a run artifact so a dispatcher gets a machine-readable result. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_015zSJ4W6qpxdCMJ1XbXnU5X
`workflow_dispatch` gained a `candidate` input alongside `baseline`, each taking a commit SHA or an exact release tag. Both default to what an automatic run would have used — candidate to the dispatched ref's own published release, baseline to the latest published master release before it — so a no-input dispatch still re-runs `push`, while naming both turns the workflow into "compare these two builds". The candidate resolves before the baseline, because an explicit candidate also moves the point the baseline walk searches back from. Comparing release N against the latest master release would otherwise be wrong whenever N is not the newest build. Both sides now need the same commit -> CI run -> tag -> is-it-published resolution and the same asset download, so that logic moves into a sourced `perf-release-lib.sh` rather than being copied. It is unit-tested with `gh`/`git` stubbed (`perf-release-lib.test.sh`, wired into CI as `perf_lib`): the paths worth freezing are the failure ones — an Actions API error, an expired artifacts token, a partially published release — and none of them can be provoked on demand against the live API. Guards added for the ways a two-sided comparison goes quietly wrong: - A candidate named by tag whose commit is absent from the checkout now fails instead of searching back from master's head. That fallback could select a baseline NEWER than the candidate, inverting every delta's sign while reporting an ordinary verdict. - Candidate and baseline resolving to the same release is refused rather than burning three legs to prove a build equals itself. - A baseline that is not an ancestor of the candidate is allowed (comparing two release lines is legitimate) but says so in the report. Fixes found by testing the extracted library: - `run_for_sha` treated an Actions API failure as "this commit has no CI run" — the same misdiagnosis `release_exists` already guards against, on the most common dispatch of all. - `.workflow_runs | first | @tsv` renders a null run as the non-empty string "\t\t-", which read as a successful lookup and produced `CI run #- ... is still —`. Fixed with `select(. != null)`. - `fetch_assets` discarded `gh release download`'s exit status, so a transient failure reported as "the release is missing assets", and it shared stdout with `gh`, where one future line of chatter would flip every caller's missing-asset test. - An unguarded `version=$(version_for_run …)` could carry an empty tag into the error messages, since `set -e` does not reach inside `$( )`. Diagnosability: `verdicts/resolution.json` records what was actually compared (both versions, SHAs, and how each was chosen) on every trigger, so a dispatcher reads the result instead of scraping markdown; the artifact now uploads even when no baseline resolved, which is when that answer matters most. The report names the candidate on the failure path too. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_015zSJ4W6qpxdCMJ1XbXnU5X
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.
Problem
The perf gate's baseline was the previous commit —
github.event.beforeon push, the exact merge-base on PRs — and its release tag was re-derived from that single commit's CI run. Both assumptions fail routinely:59585cc6is on master with zero CI runs — verified against the live API.Either case failed the job outright with "no baseline found". That is how this gate has spent most of its life: reporting a data-availability problem rather than comparing anything.
Change
Baseline = the latest published master release at/before the baseline point. Walk master-push CI runs newest-first and keep the first ancestor whose re-derived tag exists as a release. PRs still try the exact merge-base first and only walk when it has none — and say so in the report, since a walked baseline widens what the gate attributes to the PR.
Manual runs choose both sides.
workflow_dispatchtakescandidateandbaseline, each a commit SHA or an exact release tag. Both default to what an automatic run would have used, so a no-input dispatch re-runs whatpushwould have, while naming both turns the workflow into "compare these two builds":Nothing is built on a dispatch, so both sides must already be published. The candidate resolves before the baseline, because an explicit candidate also bounds where the baseline walk searches back from — comparing release N against the latest master release would otherwise be wrong whenever N is not the newest build.
Naming a commit works for master commits only: a PR run's tag derives from its ephemeral merge commit, which cannot be re-derived from the branch head the runs API exposes. PRs use the
perf-testlabel, unchanged, or pass a release tag directly.Guards against quietly-wrong comparisons
Shared library, and its tests
Both sides now need the same commit → CI run → tag → is-it-published resolution and the same asset download, so it lives in
perf-release-lib.shinstead of two copies. It is unit-tested withgh/gitstubbed (perf-release-lib.test.sh, wired into CI asperf_lib, ~5s). The paths worth freezing are the failure ones, and none can be provoked on demand against the live API.Extracting it immediately surfaced four bugs:
run_for_shareported an Actions API failure as "this commit has no CI run" — the same misdiagnosisrelease_existsalready guards against, reachable on the most common dispatch of all..workflow_runs | first | @tsvrenders a null run as the non-empty string"\t\t-", which read as a successful lookup and producedCI run #- … is still —. Fixed withselect(. != null).fetch_assetsdiscardedgh release download's exit status, so a transient failure reported as "the release is missing assets"; it also shared stdout withgh, where one future line of chatter would flip every caller's missing-asset test.version=$(version_for_run …)could carry an empty tag into the error messages —set -edoes not reach inside$( ).Other supporting fixes
no published master release foundaimed at the wrong system.gh apigets its own message instead of an empty read.gh release downloadexits 0 when only some patterns matched, so a partially published release died on a barecp: cannot stat. Baseline degrades, candidate fails, both naming what is absent..name=="CI"string coupling, and the 50-run window is no longer shared with the other push-triggered workflows (autodoc roughly halved it).Diagnosability
verdicts/resolution.jsonrecords what was actually compared — both versions, SHAs, and how each was chosen — on every trigger, so a dispatcher reads a stable JSON result instead of scraping markdown. The artifact uploads even when no baseline resolved, which is when that answer matters most. The report names the candidate on the failure path too, and PostHog carriescandidate_version/baseline_version.Verification
perf-release-lib.test.shcovers 19 cases (green on bash 5.3 and macOS bash 3.2, and in CI). On top of that, every step's script was extracted from the YAML and run against the live GitHub API:v1.0.0-alpha-build.312.1247+gd46c6c4f+gancestor=false+ report noteBad credentials (HTTP 401), exit 1 — not "no release"ok=false, does not fail the stepPlus YAML parse and
bash -nover everyrun:block.Note
This PR's own Perf legs only run if the
perf-testlabel is applied — worth adding before merge so the auto-resolution path proves itself end to end.🤖 Generated with Claude Code
https://claude.ai/code/session_015zSJ4W6qpxdCMJ1XbXnU5X