From f8dbed1134694d7917a01fa8a6eacbf4bf61ab5e Mon Sep 17 00:00:00 2001 From: Imani Manyara Date: Tue, 13 Oct 2026 11:59:56 -0400 Subject: [PATCH] Skip the Dependabot merge gate until its app is configured Every scheduled run failed one merge () job per org with "'client-id' must be set to a non-empty string", because MERGE_APP_CLIENT_ID and MERGE_APP_PRIVATE_KEY are not set on this repository yet. The accounts job now checks both are present (only whether each is set reaches the step), posts a notice and skips the merge jobs when either is missing, and the gate runs as before once both exist. The schedule moves from hourly to weekly, Tuesday 14:17 UTC, a day after Dependabot's Monday run and the reconciler, per the org cap of weekly crons. Every failure was also an email. --- .github/workflows/dependabot-merge.yml | 31 +++++++++++++++++++++++++- dependabot/README.md | 6 ++--- 2 files changed, 33 insertions(+), 4 deletions(-) diff --git a/.github/workflows/dependabot-merge.yml b/.github/workflows/dependabot-merge.yml index 44fe6d0..5f4a6ea 100644 --- a/.github/workflows/dependabot-merge.yml +++ b/.github/workflows/dependabot-merge.yml @@ -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: @@ -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 @@ -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: diff --git a/dependabot/README.md b/dependabot/README.md index 8c36ef5..8008242 100644 --- a/dependabot/README.md +++ b/dependabot/README.md @@ -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 `. | | `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`. @@ -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. @@ -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