Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
20 changes: 20 additions & 0 deletions .github/dependabot.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
version: 2
updates:
- package-ecosystem: "pip"
directory: "/"
schedule:
interval: "weekly"
open-pull-requests-limit: 5
ignore:
- dependency-name: "*"
update-types:
- "version-update:semver-major"
- package-ecosystem: "github-actions"
directory: "/"
schedule:
interval: "weekly"
open-pull-requests-limit: 5
ignore:
- dependency-name: "*"
update-types:
- "version-update:semver-major"
84 changes: 84 additions & 0 deletions .github/workflows/dependabot_auto_merge.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
name: Auto Merge Dependabot PR

"on":
workflow_run:
workflows: ["CI"]
types: [completed]

jobs:
auto-merge:
if: github.event.workflow_run.conclusion == 'success' && startsWith(github.event.workflow_run.head_branch, 'dependabot/')
runs-on: ubuntu-latest
timeout-minutes: 10
permissions:
contents: write
pull-requests: write

steps:
- name: Resolve Dependabot PR
id: pr
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
set -euo pipefail
BRANCH_NAME="${{ github.event.workflow_run.head_branch }}"
PR_NUMBER=$(gh pr list --repo "${GITHUB_REPOSITORY}" --state open --head "${BRANCH_NAME}" --json number --jq '.[0].number // empty')
if [ -z "${PR_NUMBER}" ]; then
echo "No open Dependabot PR found for ${BRANCH_NAME}." >> "$GITHUB_STEP_SUMMARY"
exit 0
fi
echo "pr_number=${PR_NUMBER}" >> "$GITHUB_OUTPUT"

- name: Evaluate merge eligibility
id: merge_guard
if: steps.pr.outputs.pr_number != ''
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
set -euo pipefail
gh pr view "${{ steps.pr.outputs.pr_number }}" --repo "${GITHUB_REPOSITORY}" --json number,isDraft,author,url,body,labels > pr.json
python3 - <<'PY'
import json
import os
from pathlib import Path

pr = json.loads(Path("pr.json").read_text(encoding="utf-8"))
author = (pr.get("author") or {}).get("login")
labels = {item.get("name", "") for item in pr.get("labels", [])}
body = pr.get("body") or ""
is_major = "update-type: version-update:semver-major" in body
dependabot_authors = {"dependabot[bot]", "app/dependabot"}
is_dependabot = author in dependabot_authors and "dependencies" in labels
should_merge = is_dependabot and not pr.get("isDraft") and not is_major

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Verify the tested head before merging

When Dependabot pushes a new commit to the same branch after the CI run that triggered this workflow, this guard still marks the PR mergeable because it only checks author/labels/body and never compares github.event.workflow_run.head_sha to the PR's current head SHA, such as headRefOid. The final gh pr merge then merges whatever commit is currently at the branch tip, so a stale successful CI run can merge an untested dependency update.

Useful? React with 👍 / 👎.

if should_merge:
reason = "ready"
elif is_major:
reason = "major_update"
else:
reason = "not_eligible_dependabot_pr"

summary_lines = [
"## Auto-Merge Gate",
f"- PR: {pr['url']}",
f"- Author: `{author or '<unknown>'}`",
f"- Draft: `{'yes' if pr.get('isDraft') else 'no'}`",
f"- Dependabot label: `{'yes' if 'dependencies' in labels else 'no'}`",
f"- Major update: `{'yes' if is_major else 'no'}`",
f"- Final merge decision: `{'merge' if should_merge else 'skip'}`",
f"- Reason: `{reason}`",
]
Path("pr-summary.md").write_text("\n".join(summary_lines).strip() + "\n", encoding="utf-8")
with open(os.environ["GITHUB_OUTPUT"], "a", encoding="utf-8") as output:
print(f"should_merge={'true' if should_merge else 'false'}", file=output)
print(f"reason={reason}", file=output)
PY

- name: Append merge summary
if: steps.pr.outputs.pr_number != ''
run: cat pr-summary.md >> "$GITHUB_STEP_SUMMARY"

- name: Merge Dependabot PR
if: steps.merge_guard.outputs.should_merge == 'true'
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: gh pr merge "${{ steps.pr.outputs.pr_number }}" --repo "${GITHUB_REPOSITORY}" --rebase --delete-branch