Skip to content

feat(dependabot-auto-merge): opt-in major gate - #191

Merged
twistedmelonman merged 1 commit into
mainfrom
claude/feat-dependabot-major-optin-120
Oct 12, 2026
Merged

twistedmelonman merged 1 commit into
mainfrom
claude/feat-dependabot-major-optin-120

Conversation

@twistedmelonman

Copy link
Copy Markdown
Member

Advances smartwatermelon/dev-env#120 (phase 1: the release valve for majors).

What changes

New caller input allow_major (boolean, default false). With it on, a single-dependency major bump outside trusted_namespaces merges without a human, but only through a stricter gate than patch/minor:

  1. The policy step emits decision=major-gate (one dependency, both versions parse, majors differ).
  2. A new step waits until every check on the PR head SHA has finished, required or not, except this workflow's own runs. Any fail or cancel leaves the PR for manual review and names the checks. SKIPPED/NEUTRAL do not block.
  3. A new step approves and merges directly with --match-head-commit <sha>. It does not use --auto.

Unchanged: callers that leave the input unset, grouped PRs, unparseable versions, and trusted-namespace majors (they keep the --auto path). The new steps run only on decision=major-gate.

Why not --auto for majors

gh pr merge --auto waits only on required checks. Measured today on main:

Repo Required on main Checks on its latest Dependabot PR
projectinsomnia standards-check build, netlify-site-checks, 4 Netlify checks, standards-check, auto-merge
amelia-boone standards-check Code standards & build (24), netlify-site-checks, 4 Netlify checks, standards-check, auto-merge
claude-config standards-check, tests standards-check, tests, auto-merge
github-workflows standards-check standards-check, guard-no-checkout

A red build is not required in three of the four repos, so it does not stop --auto. Auto-merge also stays armed across a Dependabot rebase, and the new head is then judged by required checks only. The header comment said --auto merges "only after all status checks pass"; that was wrong and now says required checks only.

How the gate avoids a false "all green"

  • Late checks. It waits 120 s, then needs two identical all-finished polls with at least one other check green. On projectinsomnia#195 and amelia-boone#103/fix: warn on too-short sha= marker in Minimize PASS review comment step #104, the last check registered 6–45 s after the PR opened.
  • Permissions. The job has no checks: read, and it cannot request one: a called job that requests a permission its caller did not grant fails validation for every caller. On a private repo the check list may come back blank. The gate therefore requires that it can see its own check run, and skips if it cannot. Private-repo opt-in may need a later contract that adds checks: read and updates callers.
  • Other exits. If the head moves, the output is unreadable, or 40 minutes pass, the gate skips. Every skip is a ::notice::, never a red check (the dependabot-auto-merge is inert in 29/51 fleet repos: can_approve_pull_request_reviews unset by installer #87 rule). The job gets timeout-minutes: 50.

Open question, not decided here

Security updates bypass Dependabot cooldown. Per GitHub's docs, cooldown applies to version updates only. A security-driven major can open on day 0, and with allow_major: true it merges as soon as every check is green. The workflow cannot tell whether a cooldown elapsed. Also, the docs list semver-major-days as not supported for github-actions; that ecosystem gets only default-days.

Testing

  • New tests/test-dependabot-auto-merge.sh (17 cases). It extracts the policy and gate run: blocks with yq and runs them against a stub gh. Four deliberate mutations (self-visibility, allow_major, the two-poll rule, cancel counting) each make it fail.
  • bash tests/run-tests.sh: all 8 files pass.
  • bash standards/run-standards.sh --repo .: clean.
  • actionlint, zizmor, and shellcheck -S info are clean, with no suppressions added.

Release

No tags were moved. Per README "Rollout discipline", pin 2–3 pilots to a new exact tag before moving dependabot-auto-merge-v2. Moving v2 also picks up two comment-only commits already on main (#180, #188). allow_major is an error on any ref that lacks it, so callers can set it only after the tag ships.

Add an allow_major input (boolean, default false). When a caller sets
it, a single-dependency major bump outside trusted_namespaces no longer
stops at "manual review". The job waits until every check on the PR
head SHA has finished, required or not, then merges directly with
--match-head-commit. Callers that leave the input unset take exactly
the old paths; the new steps run only on decision=major-gate.

Why a new gate instead of --auto: --auto waits only on required
checks, and most fleet repos require only standards-check. A red build
does not stop it, and an armed auto-merge survives a Dependabot rebase.

How the gate decides:
- Any fail or cancel: leave for manual review, naming the checks.
- SKIPPED and NEUTRAL do not block (Netlify's "Pages changed").
- Passes only after a settle wait and two identical all-finished polls,
  with at least one other check green. Checks such as Netlify's appear
  minutes after the PR opens.
- Must see its own check run. The token has no checks permission, and
  the job cannot ask for one without breaking callers that do not grant
  it, so a blank list on a private repo becomes manual review, not a
  false "all green".
- Head moved, unreadable output, or 40 minutes elapsed: manual review.
  Every exit is a ::notice::, never a red check (the #87 rule).

Grouped PRs are unchanged. Trusted-namespace majors keep the --auto
path. The job gains timeout-minutes: 50 to bound the wait.

The header comment said --auto merges "only after all status checks
pass". That is wrong; it now says required checks only. The header and
README also note that security updates bypass Dependabot cooldown, so
an allow_major repo can merge a security-driven major on day 0.

tests/test-dependabot-auto-merge.sh extracts the two inline run:
blocks with yq and runs them against a stub gh (17 cases). Mutating
the self-visibility, allow_major, two-poll, and cancel conditions each
makes it fail.

Advances smartwatermelon/dev-env#120
@twistedmelonman
twistedmelonman merged commit 47d9f27 into main Oct 12, 2026
2 checks passed
@twistedmelonman
twistedmelonman deleted the claude/feat-dependabot-major-optin-120 branch October 12, 2026 19:37
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.

1 participant