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
31 changes: 30 additions & 1 deletion .github/workflows/dependabot-merge.yml
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,18 @@ name: Dependabot merge gate
#
# Dry run unless `apply` is ticked on a manual run, or, for scheduled runs, the repository
# variable DEPENDABOT_MERGE_APPLY is 'true'. Going live is that variable, not a code change.
#
# Until the Dependency Merge Bot exists (variable MERGE_APP_CLIENT_ID and secret
# MERGE_APP_PRIVATE_KEY on this repository), every run posts a notice and skips the merge jobs
# instead of failing one per org. Creating the app and setting both is the owner's step:
# dependabot/README.md, "Credentials".

on:
schedule:
- cron: "17 * * * *"
# Weekly, Tuesday 14:17 UTC: a day after Dependabot's Monday 06:00 America/New_York run and the
# reconciler's Monday 14:00 UTC run, so the week's pull requests have finished CI. The org
# caps crons at weekly; anything more urgent is a manual run.
- cron: "17 14 * * 2"
workflow_dispatch:
inputs:
apply:
Expand All @@ -33,7 +41,27 @@ jobs:
contents: read
outputs:
orgs: ${{ steps.list.outputs.orgs }}
ready: ${{ steps.credentials.outputs.ready }}
steps:
# Only whether each is set reaches this step, never the key itself.
- name: Check the merge app credentials are configured
id: credentials
env:
HAS_CLIENT_ID: ${{ vars.MERGE_APP_CLIENT_ID != '' }}
HAS_PRIVATE_KEY: ${{ secrets.MERGE_APP_PRIVATE_KEY != '' }}
run: |
if [ "$HAS_CLIENT_ID" = "true" ] && [ "$HAS_PRIVATE_KEY" = "true" ]; then
echo "ready=true" >> "$GITHUB_OUTPUT"
exit 0
fi
missing=()
[ "$HAS_CLIENT_ID" = "true" ] || missing+=("variable MERGE_APP_CLIENT_ID")
[ "$HAS_PRIVATE_KEY" = "true" ] || missing+=("secret MERGE_APP_PRIVATE_KEY")
msg="Merge gate skipped: ${missing[*]} not configured on this repository. See dependabot/README.md, Credentials."
echo "::notice title=Dependabot merge gate::$msg"
echo "$msg" >> "$GITHUB_STEP_SUMMARY"
echo "ready=false" >> "$GITHUB_OUTPUT"

- uses: actions/checkout@v7
with:
persist-credentials: false
Expand All @@ -46,6 +74,7 @@ jobs:
merge:
name: merge (${{ matrix.org }})
needs: accounts
if: ${{ needs.accounts.outputs.ready == 'true' }}
runs-on: ubuntu-latest
timeout-minutes: 20
permissions:
Expand Down
6 changes: 3 additions & 3 deletions dependabot/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ Both tools are dry runs unless told otherwise, and neither ever pushes to a defa
| `merge.py` | For one account, finds open pull requests by `dependabot[bot]`, decides each with the pure function `decide()`, and with `--apply` merges eligible ones with `gh pr merge --squash --match-head-commit <sha>`. |
| `common.py` | Policy loading and validation, the `gh` wrapper, repository listing and the active filter, summary output. |
| `.github/workflows/dependabot-reconcile.yml` | Monday 14:00 UTC (after Dependabot's own 06:00 America/New_York run) and on demand, one job per org. |
| `.github/workflows/dependabot-merge.yml` | Hourly at :17 and on demand, one org at a time, never two runs at once. |
| `.github/workflows/dependabot-merge.yml` | Weekly, Tuesday 14:17 UTC, and on demand, one org at a time, never two runs at once. Skips with a notice while the merge app's credentials are not configured. |
| `.github/workflows/ci.yml` | Unit tests and actionlint on every pull request to this repository. |

Every run prints a table and appends it to the job summary: for the reconciler `repo · status · note`, for the merge gate `repo · PR · action · reason`.
Expand Down Expand Up @@ -49,7 +49,7 @@ Client accounts (`adelsaiq`, `dannielagumo`, `joemanch`) are refused by both scr
3. Levels: `off` allows nothing; `actions` allows non-major GitHub Actions updates; `patch` adds other ecosystems' patches; `minor` adds their minors. Majors never. Override precedence is `owner/repo`, then `owner/*`, then `default`.
4. Every check run on the head SHA has completed with `success`, `neutral` or `skipped`, and every commit status is `success`. Anything pending holds.
5. At least as many check runs as the floor: the count on the head of the repository's most recently merged pull request not authored by Dependabot. With no such pull request the floor is 1 and one check name must look like a test (`test`, `pest`, `phpunit`, `pytest`, `jest`, `vitest`, `spec`).
6. `mergeable_state` is `clean`. GitHub computes it lazily, so a first read of `unknown` holds until the next hourly run.
6. `mergeable_state` is `clean`. GitHub computes it lazily, so a first read of `unknown` holds until the next run.

A workflow that is path-filtered off a Dependabot pull request lowers its check count below the floor, and the pull request is held for a person. That is the intended failure direction.

Expand Down Expand Up @@ -90,7 +90,7 @@ Both workflows ship as dry runs.
| Merge gate on schedule | Set the repository variable `DEPENDABOT_MERGE_APPLY` to `true`. |
| Back to dry run | Delete the variable or set it to anything else. |

**Prerequisite for the merge gate:** Refresh Bot's manifest grants `contents` and `pull_requests` only. The gate also needs **checks: read**, **statuses: read** and **workflows: write** (merging an Actions update changes `.github/workflows/`, which GitHub refuses an App without it). Until the App is granted those and each org approves the new permissions, the token step fails rather than the gate running half-blind. The reconciler needs nothing beyond the current two.
**Prerequisite for the merge gate:** Refresh Bot's manifest grants `contents` and `pull_requests` only. The gate also needs **checks: read**, **statuses: read** and **workflows: write** (merging an Actions update changes `.github/workflows/`, which GitHub refuses an App without it). Until the App is granted those and each org approves the new permissions, the token step fails rather than the gate running half-blind. While `MERGE_APP_CLIENT_ID` or `MERGE_APP_PRIVATE_KEY` is not set at all, the gate posts a notice and skips its merge jobs instead. The reconciler needs nothing beyond the current two.

## The personal account

Expand Down
Loading