Dependabot auto-merge: AI risk gate for every major bump + CI gate on every merge#9
Conversation
The AI risk gate (#2229) decides *eligibility* from the changelog, but the actual merge used `gh pr merge --auto`, which only waits for *required* status checks. Across the org almost no repo has required checks configured, so today a patch/minor or AI-approved major bump auto-merges even when CI is red -- the tests run but never block the merge. Add review-tooling/gated_merge.sh and route every auto-merge (patch/minor AND AI-approved majors) through it. It waits for the PR's own checks -- every check except this workflow's own run, matched by run id in the check link -- to reach a terminal state, then merges only if all passed; any failed/cancelled check, or a timeout, routes the PR to `needs-manual-review` instead. Why read check conclusions here instead of GitHub required checks: required checks are matched by exact name per repo, and this workflow is injected org-wide across repos whose test jobs are named differently or absent. Requiring a check a repo does not emit would deadlock its PRs. Reading the PR's actual checks works uniformly regardless of naming -- so this gives org-wide "don't merge over red CI" with zero per-repo config. - Fail-closed: the script always exits 0 (this job is itself a required workflow), routing to human on failure/timeout rather than merging. - Kill switch: CI_GATE_ENABLED=false falls back to native `--auto`. - No-CI repos: after a grace period with no checks, merge on the existing signal (patch/minor policy or AI verdict) -- unchanged behavior. - Per-PR concurrency cancels superseded runs so two runs never race to merge. shellcheck + actionlint clean. Decision paths (green/red/pending/no-checks) dry-run-verified against live `gh pr checks` data.
Drop AIGATE_ALLOWLIST so the AI verdict runs for MAJOR github-actions bumps in every repo the shared workflow is injected into, not just the RealRate-Private pilot. patch/minor (auto, CI-gated) and major pip/docker (comment + human) are unchanged. The generic major-bump comment/label now fire only for non-github-actions majors, since github-actions majors are handled by the AI block in every repo. Still fail-closed (AI error -> human) and enforce/observe is still controlled by AIGATE_DRY_RUN. actionlint clean.
Update: widened the AI gate to all repos (2nd commit
|
| Bump | Repo | Path |
|---|---|---|
| patch / minor | any | auto-approve → CI-gated merge |
major, github-actions |
any | AI verdict → auto merges (CI-gated), else human |
major, pip/docker |
any | comment + needs-manual-review |
Still fail-closed (AI error → human); enforce vs observe still controlled by AIGATE_DRY_RUN (currently false = enforcing). actionlint clean.
AIGATE_DRY_RUN: "true" before merge and flip it after watching a few real bumps.
Extend the AI verdict to all major Dependabot bumps in every ecosystem, not just
github-actions: drop the `package-ecosystem == 'github_actions'` condition from
the AI verdict / auto-merge / route-to-human steps, and remove the now-redundant
generic major-bump comment+label steps (every major is handled by the AI block).
Generalize the classifier prompt from GitHub-Actions-specific ("pinned Action",
"inputs/outputs", "Node version") to ecosystem-aware: it now judges a library
(pip), a container base image (docker), or a CI action, and is told the
ecosystem via a new --ecosystem arg fed from fetch-metadata. Same strictness:
any breaking public-API / input / default / platform-support / security change,
or any uncertainty, -> human.
patch/minor unchanged (auto, CI-gated). Still fail-closed (AI error -> human);
still CI-gated by gated_merge.sh; enforce/observe via AIGATE_DRY_RUN.
actionlint clean; script compiles; fail-closed fallback verified offline.
Update: AI gate now covers every major bump, all ecosystems (commit
|
| Bump | Repo | Path |
|---|---|---|
| patch / minor | any | auto-approve → CI-gated merge |
| major (pip / docker / github-actions) | any | AI verdict → auto merges (CI-gated), else human |
Still fail-closed (AI error → human), still CI-gated by gated_merge.sh, enforce/observe via AIGATE_DRY_RUN. actionlint clean; script compiles; fail-closed fallback verified offline.
pip major bumps are qualitatively riskier than github-actions bumps: they're real code-API breaks, and (per RealRate-Private#2244's coverage analysis) our test suite mocks the IO-tier libs (requests/yfinance/sec_api/google) and doesn't exercise scipy — so for those, green CI is weaker evidence and the AI verdict is carrying more weight. Strongly suggest shipping AIGATE_DRY_RUN: "true" first to observe pip/docker verdicts before enforcing.
Cosmetic: the repo's dependabot.yml declares package-ecosystem: uv for Python (plus github-actions, docker), so update the classifier docstring/prompt and the workflow header from 'pip' to 'Python (uv/pip)'. No behavior change -- the AI gate is ecosystem-agnostic (the ecosystem is a hint, not a gate), so it already covers uv bumps regardless of the exact string fetch-metadata reports.
|
Correction (commit 2614081): this repo's |
Roll out the org-wide, all-ecosystem AI gate in dry-run first: major bumps get their AI verdict posted as a comment but are NOT auto-merged (routed to human), so we can watch the verdicts on real bumps -- especially uv/Python, where the test suite has known blind spots -- before enforcing. Flip to "false" to enforce. patch/minor auto-merge and the CI gate are unaffected by this flag.
|
Set to observe mode ( |
…=false) Confirmed target: every MAJOR bump (libraries/uv, github-actions, docker; any repo) -> AI risk check -> tests -> auto-merged if the AI judges it low-risk AND CI passes; AI-flagged risk or a failed check routes to a human. patch/minor auto-merge + the CI gate unaffected.
Final aligned behavior (commit
|
| Bump | Flow | Outcome |
|---|---|---|
| patch / minor (any ecosystem) | approve → CI | auto-merge if CI green |
| major — libraries (uv) | AI risk check → CI | auto-merge if AI low-risk AND CI green, else human |
| major — github-actions | AI risk check → CI | auto-merge if AI low-risk AND CI green, else human |
| major — docker | AI risk check → CI | auto-merge if AI low-risk AND CI green, else human |
- AI-flagged risk, or any failed check, or an AI error → routed to a human (
needs-manual-review), never merged. - Applies org-wide (every repo the shared workflow is injected into).
AIGATE_DRY_RUN: "false"(enforcing) ·CI_GATE_ENABLED: "true"(CI gate on).
Both flags remain as fast kill switches if anything misbehaves. Ready for review.
Remove .github/review-tooling/__pycache__/review_dependabot_major.cpython-314.pyc, accidentally committed in f6ba898 when the script was compiled locally during validation. Add a .gitignore (__pycache__/, *.py[cod]) so Python bytecode is never committed again.
Comment-only. Spell out in requirements.txt that the openai package is used solely as the client library against the GitHub Models endpoint with the built-in GITHUB_TOKEN -- no OpenAI API key/account/billing. Prevents the recurring 'why is openai here' confusion for reviewers.
Align the classifier with the org's uv standard: declare its single dep inline via PEP 723 (dependencies = ["openai==2.45.0"]) and run it with `uv run --script`, provisioned by a pinned astral-sh/setup-uv step. Delete requirements.txt and drop the pip install. --script forces script mode so uv ignores the caller repo's pyproject/project env (verified: runs fine even from a dir with an unresolvable pyproject). Validated end-to-end: uv run --script provisions openai in an isolated env and the fail-closed path still returns verdict=human with no token. actionlint clean.
AI tooling now uses uv (not pip/requirements.txt) — commit
|
Problem
The AI risk gate (RealRate-Private#2229) is live and decides eligibility for major
github-actionsbumps from the changelog. But the actual merge, for both patch/minor and AI-approved majors, usedgh pr merge --auto— which only waits for required status checks.I verified that across our repos there are effectively no required status checks configured (RealRate-Private's
develophas none — no ruleset check, no classic branch protection). So today a bump auto-merges even when CI is red. The tests run; they just don't block anything. The AI verdict is effectively the only gate.Why not just "make the tests a required check"?
Required checks are matched by exact name, per repo. This workflow is injected org-wide across repos whose test jobs are named differently (or that have none). A single org rule can't name them all, and requiring a check a repo doesn't emit would deadlock every PR in that repo. So required-checks don't scale to the org here.
Approach — read the PR's own checks in the shared workflow
New
review-tooling/gated_merge.sh; every auto-merge path (patch/minor and AI-approved majors) now routes through it instead ofgh pr merge --auto. It:link) — to reach a terminal state.fail/cancelbucket, or a timeout, routes the PR toneeds-manual-reviewinstead.Because it reads the PR's actual check conclusions, it works uniformly regardless of what each repo names its checks — org-wide "don't merge over red CI" with zero per-repo config. This is the "readiness" half of the #2229 design ("AI decides eligibility; CI decides readiness"); the AI still does the changelog judgment, this just refuses to merge over red CI.
Safety properties
CI_GATE_ENABLED: "false"reverts to native--autoorg-wide via one env flip, no revert needed.concurrencycancels superseded runs so two runs never race to merge the same PR.Resulting behavior on a Dependabot PR
github-actions, AIauto, CI greengithub-actions, AIauto, CI redhuman/uncertain/error, or non-allowlisted majorValidation
shellcheck+actionlintclean.gh pr checksJSON.opened). Suggest merging withCI_GATE_ENABLEDfirst observed on the next real bump, or temporarily pointing a test bump at it.Honest caveats to review
GATE_APPEAR_SECONDS(180s) to register, the script could treat it as a no-CI repo and merge. Dependabot checks normally register in seconds; tunable, and the robust-but-non-scaling alternative is native required checks per repo.concurrency: cancel-in-progresscancels the in-flight run; for an AI-gated major the verdict is computed only onopened, so a mid-wait rebase drops to manual review (safe, but no auto-merge on that PR).Refs: RealRate-Private#2244 (this CI gate), RealRate-Private#2229 (AI gate). Ships behind
CI_GATE_ENABLED(default on).🤖 Generated with Claude Code