From 840e90839a971bda5070b0e6932fe76c99a4588d Mon Sep 17 00:00:00 2001 From: David Leong <116610336+leongdl@users.noreply.github.com> Date: Fri, 4 Sep 2026 15:58:10 -0700 Subject: [PATCH] ci: Do not auto-approve cargo minor bumps `auto_approve.yml` approves any PR authored by `dependabot[bot]` with no filter on update type. `fetch-metadata` is already wired up with `id: metadata`, but none of its outputs are read. That is about to matter. Once dependabot watches `cargo`, the highest-risk update class it can produce is a minor bump of `openjd-expr`, `openjd-model` or `openjd-sessions`: those crates are 0.x, where cargo treats a minor as breaking, and they carry the API surface the Rust bindings wrap. openjd-expr 0.4.0 carried a breaking coercion change and openjd-model 0.5.2 changed the job-side `StepScript` wire format. Under the current workflow such a PR arrives pre-approved, so the update most needing a human to look is the one least likely to get one. Gate the approval step on the ecosystem and the update type. Because `update-type` reports the highest semver change in the PR, a grouped cargo PR containing any minor is held back as well as a solo one. Scoped to cargo rather than gating every ecosystem on patch. The pip and github-actions entries group minor with patch into one PR, so a global patch-only gate would stop auto-approving nearly every PR from them -- a much larger behaviour change than the problem warrants, and not one this addresses. Known gap left in place: a pip or github-actions major is still auto-approved, as it is today. Gating that is a one-line change but a separate decision, so it is deliberately not bundled here. Testing: actionlint passes on the modified file, and on the unmodified file as a control. Mutating the step reference to a name that does not exist makes actionlint fail at that line, which confirms it is checking the expression rather than skipping it. actionlint types step outputs as `{string => string}` and so cannot confirm `package-ecosystem` is a real output; that was checked against fetch-metadata's README at the pinned v3, which documents both `package-ecosystem` and `update-type` as "the highest semver change being made by this PR". The gate itself was checked by parsing the expression back out of the YAML and evaluating it over 10 PR shapes: openjd-* minors solo and grouped, a cargo major, a cargo patch group, and the pip and github-actions cases that must keep flowing. All 10 behave as intended. GitHub Actions cannot be run locally, so the live behaviour is unverified until a dependabot PR opens. Signed-off-by: David Leong <116610336+leongdl@users.noreply.github.com> --- .github/workflows/auto_approve.yml | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/.github/workflows/auto_approve.yml b/.github/workflows/auto_approve.yml index b4838ce3..152f128d 100644 --- a/.github/workflows/auto_approve.yml +++ b/.github/workflows/auto_approve.yml @@ -15,6 +15,15 @@ jobs: with: github-token: "${{ secrets.GITHUB_TOKEN }}" - name: Approve a PR + # Hold back cargo minors and majors: the openjd-* crates are 0.x, where + # cargo treats a minor as breaking, so those most need a human to look. + # `update-type` is the highest change in the PR, so a grouped cargo PR + # containing a minor is held back too. Scoped to cargo because the pip + # and github-actions groups bundle minor with patch, so gating on patch + # alone would stop auto-approving nearly every PR from them. + if: >- + steps.metadata.outputs.package-ecosystem != 'cargo' || + steps.metadata.outputs.update-type == 'version-update:semver-patch' run: gh pr review --approve "$PR_URL" env: PR_URL: ${{ github.event.pull_request.html_url }}