Skip to content

fix(ci): make the dependabot auto-merge guard refuse instead of merging - #372

Merged
forkwright merged 1 commit into
mainfrom
fix/auto-merge-guard
Aug 14, 2026
Merged

fix(ci): make the dependabot auto-merge guard refuse instead of merging#372
forkwright merged 1 commit into
mainfrom
fix/auto-merge-guard

Conversation

@forkwright

@forkwrightforkwright commented Aug 14, 2026

Copy link
Copy Markdown
Owner

The "Wait for CI checks to pass" step printed a refusal and then exit 0,
which makes the STEP SUCCEED.

The merge steps below it gate only on steps.metadata.outputs.update-type
-- never on this step's outcome. A step whose if: contains no status
function carries an implicit success(), so a later step is skipped only
when an earlier one FAILS. A guard that exits zero therefore announced
"skipping auto-merge" and then ran gh pr merge --squash.

Nothing has merged wrongly, and the reason is worth stating precisely
rather than as a near-miss: main is protected with required contexts
(cargo audit, cargo deny, gate / gate), which refuses the merge
independently. The defect is that this guard contributed no
defence-in-depth while its own message asserted otherwise -- a check
reporting a verdict it never enforced. Branch protection also only
enforces checks it knows about, so a required check that silently never
reports is invisible to it.

harmonia and the canonical reusable in forkwright/.github both already
carry this fix with a comment naming the same failure mode; this copy
predates them.

Refs #371

The "Wait for CI checks to pass" step printed a refusal and then `exit 0`,
which makes the STEP SUCCEED.
The merge steps below it gate only on `steps.metadata.outputs.update-type`
-- never on this step's outcome. A step whose `if:` contains no status
function carries an implicit `success()`, so a later step is skipped only
when an earlier one FAILS. A guard that exits zero therefore announced
"skipping auto-merge" and then ran `gh pr merge --squash`.
Nothing has merged wrongly, and the reason is worth stating precisely
rather than as a near-miss: `main` is protected with required contexts
(cargo audit, cargo deny, gate / gate), which refuses the merge
independently. The defect is that this guard contributed no
defence-in-depth while its own message asserted otherwise -- a check
reporting a verdict it never enforced. Branch protection also only
enforces checks it knows about, so a required check that silently never
reports is invisible to it.
harmonia and the canonical reusable in forkwright/.github both already
carry this fix with a comment naming the same failure mode; this copy
predates them.
Refs #371
@forkwright
forkwright merged commit 56147fb into mainAug 14, 2026
8 checks passed
@forkwright
forkwright deleted the fix/auto-merge-guard branch August 14, 2026 18:30
@github-actionsgithub-actionsBot mentioned this pull request Aug 14, 2026
forkwright pushed a commit that referenced this pull request Aug 16, 2026
🤖 I have created a release *beep* *boop*
---
##
[0.1.24](v0.1.23...v0.1.24)
(2026-08-14)
### Bug Fixes
* **ci:** make the dependabot auto-merge guard refuse instead of merging
([#372](#372))
([56147fb](56147fb)),
closes [#371](#371)
---
This PR was generated with [Release
Please](https://github.com/googleapis/release-please). See
[documentation](https://github.com/googleapis/release-please#release-please).
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
forkwright added a commit that referenced this pull request Aug 21, 2026
Closes#371
Replaces 55 lines of locally-copied auto-merge logic with a pinned call
to the canonical reusable in
`forkwright/.github`.
The copy is how this repo's version drifted. It kept `exit 0` on the
refusal path long after the
canonical workflow had corrected it to `exit 1` — so the guard printed
"refusing auto-merge" and then
merged, because Actions skips a later step only when an earlier one
*fails*. #372 fixed that
instance; this removes the copy that produced it, which is what the
issue actually asked for.
## Why this was blocked, and why it no longer is
The reusable used to match the osv check by exact suffix, testing for
names ending in `osv scanner`
or `osv-scan`. This repo's job is named `osv-scanner`, which ends with
neither — so adopting then
would have made auto-merge refuse every PR whose osv check had reported
and **passed**.
forkwright/.github#38 replaced that with normalisation: strip every
non-alphanumeric, lowercase, test
containment. All four tokens the reusable requires resolve here:
| required token | this repo's check | normalised |
|---|---|---|
| `gateattestation` / `gategate` | `gate / gate` | `gategate` ✓ |
| `cargodeny` | `cargo deny` | `cargodeny` ✓ |
| `cargoaudit` | `cargo audit` | `cargoaudit` ✓ |
| `osvscan` | `osv-scanner` | `osvscanner` contains it ✓ |
Those four are exactly the required contexts on `main` plus the gate, so
the reusable is checking the
same set branch protection does — with the addition it was adopted for:
it verifies the checks
**reported at all**, which branch protection cannot, since a required
check that silently never
reports is not a failure it can see.
## Two deliberate differences from `gate-attestation.yml`
Both are commented inline, because both look like inconsistencies and
neither is.
**This caller declares `concurrency`; `gate-attestation.yml`
deliberately does not.** The difference
is in the reusables. `hybrid-gate.yml` declares its own group, so a
caller-level duplicate
self-cancels it. This one declares none — so without a caller-level
block, a superseded push to a
dependabot PR leaves the earlier run racing the newer one, both waiting
on checks and both able to
merge.
**This caller does not pass `secrets: inherit`; `gate-attestation.yml`
does.** That keyword forwards
every repository secret to a workflow in another repository. This
reusable reads
`secrets.GITHUB_TOKEN` and nothing else, which a called workflow
receives regardless — so inheriting
would widen the blast radius of a change in `forkwright/.github` for no
capability gained.
## Verification
`pre-push-verify.sh` derives the crates a branch touches from its diff;
this branch touches none, so
there is nothing for it to build. The verification that matters here is
GitHub resolving the `uses:`
ref and the reusable's own jobs reporting, which only CI can do. The
YAML was parsed locally to rule
out a malformed file before pushing.
The refusal path itself is exercised by the next dependabot PR rather
than by this one — this PR is
not authored by `dependabot[bot]`, so the job's `if:` correctly skips
it. Worth watching the first
dependabot bump after this lands to confirm the guard fails rather than
skips on a red check.
Co-authored-by: forkwright <cody@forkwright.com>
Sign up for freeto 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.

1 participant

@forkwright