From 3839734e3aea2a1fdf72a567eaab50b6ec441119 Mon Sep 17 00:00:00 2001 From: Pieter Viljoen Date: Mon, 11 May 2026 16:22:01 -0700 Subject: [PATCH 1/3] Forward-only develop with dual-target Dependabot + codegen; push-vs-pull docs Codifies the branching model AGENTS.md actually implies: develop is forward-only (no main -> develop back-merges, develop ruleset squash- only blocks them anyway), and every automated content path (Dependabot scheduled updates, codegen) opens *parallel* PRs against main and develop so both branches stay current independently without any back-merging. Reasoning the historical back-merges suggested otherwise: when only develop received bot PRs, main fell out of date on dep versions and generated content until the next develop -> main release. For projects on pull-distribution channels (Docker, NuGet, PyPI) where every push to main triggers a release, that worked fine because the next release was usually imminent. For projects on push-distribution channels (HACS, distros that vendor from main directly) or with long-running develop features, main could go stale for weeks. The previous "back-merge main into develop occasionally" workaround violates the develop ruleset (merge commits forbidden on develop). The dual-target bot model is the clean fix. Workflow + config changes - .github/dependabot.yml: every ecosystem now has two entries, one with `target-branch: "main"` and one with `target-branch: "develop"`. Dependabot opens parallel PRs to each branch independently. Header comment rewritten to explain the dual-target rationale. - .github/workflows/run-codegen-pull-request-task.yml: codegen now runs as a matrix over `main` and `develop`. Branch names are codegen-main and codegen-develop; each opens its own PR against its base. `fail- fast: false` so a failure on one branch doesn't block the other. - .github/workflows/merge-bot-pull-request.yml `merge-codegen` job: accepts either head/base pair (codegen-main -> main or codegen-develop -> develop) with strict pairing so a misconfigured branch can't cross targets. Merge step now uses the same `case` statement as merge-dependabot to dispatch --squash vs --merge by base ref. Header comment block updated to reflect the dual-target model. Documentation - AGENTS.md "Branching Model": new bullet codifying forward-only develop (with explicit callout that historical back-merge commits predate the rule). New bullet describing the dual-target bot model with rationale. Existing Dependabot-targets-develop bullet rolled into the dual-target one. - README.md "Template - Branching Workflow": rewritten to match AGENTS.md exactly (squash to develop, merge-commit to main, forward- only, dual-target bots). Drops the stale "Squash and merge from develop to main" and "bots merge into main directly" lines. - README.md "Template - Release Distribution Model: Push vs. Pull" (new section): documents the push-on-merge default this template ships with and the manual-release alternative for HACS / distro- vendored projects. Walks through the exact `publish-release.yml` trigger change and the trade-offs of keeping or dropping the main-target Dependabot entries in that mode. References homeassistant-purpleair as the working example. - README.md "Template - GitHub Setup" codegen auto-merge condition: updated to show the new head/base pairing, the github.actor check, and a pointer to AGENTS.md "Branching Model" for the dual-target rationale. Rulesets section split into Develop (squash-only) and Main (merge-only) with shared settings called out separately. Co-Authored-By: Claude Opus 4.7 (1M context) --- .github/dependabot.yml | 78 +++++++++++++++---- .github/workflows/merge-bot-pull-request.yml | 56 ++++++++----- .../run-codegen-pull-request-task.yml | 27 ++++++- AGENTS.md | 4 +- README.md | 65 ++++++++++++---- 5 files changed, 174 insertions(+), 56 deletions(-) diff --git a/.github/dependabot.yml b/.github/dependabot.yml index 0c3e9331..15b75796 100644 --- a/.github/dependabot.yml +++ b/.github/dependabot.yml @@ -1,25 +1,47 @@ # https://docs.github.com/en/code-security/dependabot/dependabot-version-updates/configuration-options-for-the-dependabot.yml-file # -# `target-branch: "develop"` on every ecosystem entry routes Dependabot's -# scheduled version-update PRs to the integration branch instead of the -# repo default (`main`). That keeps dep bumps in the -# `feature → develop → main` flow described in AGENTS.md: each bump joins -# the bot-triggered develop prerelease for early-warning testing, then -# bundles into the next develop → main merge-commit alongside feature work. -# This is also what keeps develop from falling behind main — bumps land on -# develop first. +# Every ecosystem appears **twice**: once with `target-branch: "main"` +# and once with `target-branch: "develop"`. Dependabot will open +# parallel PRs against each branch, so both stay current on +# dependency versions independently of the develop → main release +# cadence. # -# Caveat: `target-branch` only redirects scheduled version updates. -# Dependabot *security update PRs* (the CVE-driven ones Dependabot opens -# in response to security alerts) are opened against the default branch -# (`main`) and do not honor `target-branch`. The merge-bot's `case` -# statement in .github/workflows/merge-bot-pull-request.yml handles -# either base correctly (squash for develop, merge for main), and a -# maintainer can retarget manually from the PR UI if a one-off needs -# the other branch. +# Why dual-target and not develop-only: +# - `develop` is the integration branch and ships content forward to +# `main` through merge-commit releases, but the time between releases +# can be long (a feature branch may sit on develop for weeks). +# - Push-distribution channels (e.g. HACS for Home Assistant +# integrations, distros that pull from main) consume `main` directly. +# If `main` only got dependency bumps via the next develop → main +# release, those channels would ship outdated code in the interim. +# - Codegen workflows take the same dual-target shape for the same +# reason — see .github/workflows/run-codegen-pull-request-task.yml. +# +# The merge-bot's `case` statement in +# .github/workflows/merge-bot-pull-request.yml dispatches the merge +# method per base ref (squash on develop, merge on main) so both bases +# auto-merge cleanly. `develop` remains strictly forward-only: there +# are no main → develop back-merges; each branch absorbs its own +# Dependabot PRs and codegen PRs independently. +# +# Security update PRs (CVE-driven) are opened by Dependabot against +# the repo default branch (`main`) regardless of any `target-branch` +# config — the `case` statement handles them in the same code path. version: 2 updates: + # ----- nuget ----- + + - package-ecosystem: "nuget" + target-branch: "main" + directory: "/" + schedule: + interval: "daily" + groups: + nuget-deps: + patterns: + - "*" + - package-ecosystem: "nuget" target-branch: "develop" directory: "/" @@ -30,6 +52,18 @@ updates: patterns: - "*" + # ----- github-actions ----- + + - package-ecosystem: "github-actions" + target-branch: "main" + directory: "/" + schedule: + interval: "daily" + groups: + actions-deps: + patterns: + - "*" + - package-ecosystem: "github-actions" target-branch: "develop" directory: "/" @@ -40,6 +74,18 @@ updates: patterns: - "*" + # ----- uv (PyPiLibrary) ----- + + - package-ecosystem: "uv" + target-branch: "main" + directory: "/PyPiLibrary" + schedule: + interval: "daily" + groups: + pypi-deps: + patterns: + - "*" + - package-ecosystem: "uv" target-branch: "develop" directory: "/PyPiLibrary" diff --git a/.github/workflows/merge-bot-pull-request.yml b/.github/workflows/merge-bot-pull-request.yml index 29d58ca7..5e012547 100644 --- a/.github/workflows/merge-bot-pull-request.yml +++ b/.github/workflows/merge-bot-pull-request.yml @@ -11,14 +11,16 @@ name: Merge bot pull request action # PRs created under `GITHUB_TOKEN` to nudge the auto-merge workflow. # # Merge method: -# Each merge step picks `--squash` or `--merge` from the PR's base ref so -# the form matches that branch's ruleset (`develop` allows only squash, -# `main` allows only merge commits — see AGENTS.md "Branching Model"). -# A mismatch fails `enablePullRequestAutoMerge` with "Merge method ... is -# not allowed on this repository". Codegen PRs always target `main` so -# they always merge-commit; Dependabot PRs default to `develop` but -# security update PRs open against `main`, so the `case` statement -# handles both bases. +# Each merge step picks `--squash` or `--merge` from the PR's base ref +# so the form matches that branch's ruleset (`develop` allows only +# squash, `main` allows only merge commits — see AGENTS.md "Branching +# Model"). A mismatch fails `enablePullRequestAutoMerge` with "Merge +# method ... is not allowed on this repository". Both Dependabot and +# codegen open parallel PRs against `main` and `develop` (see the +# AGENTS.md "Branching Model" dual-target bot section), so both jobs +# below use a `case` statement to dispatch the merge method by base +# ref. Dependabot security update PRs (always against `main`) flow +# through the same code path. on: pull_request: @@ -84,20 +86,26 @@ jobs: merge-codegen: name: Merge codegen pull request job runs-on: ubuntu-latest - # Restrict to codegen PRs that originate from the App in this repository. - # Codegen always opens PRs against `main` from the `codegen` branch. - # Both the PR author AND the event actor must be the App: the author - # check stops human-opened PRs that happen to target the `codegen` - # branch from auto-merging; the actor check stops a maintainer - # pushing extra commits to the App's `codegen` branch (a - # `synchronize` event the human triggered) from auto-merging + # Restrict to codegen PRs that originate from the App in this + # repository. Codegen runs in a matrix over `main` and `develop`, + # so two head refs are valid: `codegen-main` (always targets `main`) + # and `codegen-develop` (always targets `develop`). The head/base + # pairing is enforced strictly so a misconfigured workflow can't, + # for example, sneak a `codegen-develop` branch into `main`. + # Both the PR author AND the event actor must be the App: the + # author check stops human-opened PRs that happen to target a + # `codegen-*` branch from auto-merging; the actor check stops a + # maintainer pushing extra commits to the App's codegen branch + # (a `synchronize` event the human triggered) from auto-merging # unintended changes through the App PR. if: >- github.event.pull_request.user.login == 'ptr727-codegen[bot]' && github.actor == 'ptr727-codegen[bot]' && - github.event.pull_request.head.ref == 'codegen' && - github.event.pull_request.base.ref == 'main' && - github.event.pull_request.head.repo.full_name == github.repository + github.event.pull_request.head.repo.full_name == github.repository && + ( + (github.event.pull_request.head.ref == 'codegen-main' && github.event.pull_request.base.ref == 'main') || + (github.event.pull_request.head.ref == 'codegen-develop' && github.event.pull_request.base.ref == 'develop') + ) permissions: contents: write pull-requests: write @@ -112,7 +120,17 @@ jobs: private-key: ${{ secrets.CODEGEN_APP_PRIVATE_KEY }} - name: Merge pull request step - run: gh pr merge --auto --merge "$PR_URL" + run: | + set -euo pipefail + case "${{ github.event.pull_request.base.ref }}" in + develop) method=--squash ;; + main) method=--merge ;; + *) + echo "::error::Unsupported base branch: ${{ github.event.pull_request.base.ref }}" + exit 1 + ;; + esac + gh pr merge --auto "$method" "$PR_URL" env: PR_URL: ${{ github.event.pull_request.html_url }} GH_TOKEN: ${{ steps.app-token.outputs.token }} diff --git a/.github/workflows/run-codegen-pull-request-task.yml b/.github/workflows/run-codegen-pull-request-task.yml index 35083cdd..4b33a9f0 100644 --- a/.github/workflows/run-codegen-pull-request-task.yml +++ b/.github/workflows/run-codegen-pull-request-task.yml @@ -1,5 +1,14 @@ name: Run codegen and pull request task +# Runs codegen against `main` and `develop` in parallel via a matrix, +# opens a PR against each base (`codegen-main` branch → main, +# `codegen-develop` branch → develop). The merge-bot auto-merges +# either PR independently. This keeps both branches current on +# generated content (date stamps, API-derived data, etc.) without +# either branch falling behind the other and without main → develop +# back-merges (see AGENTS.md "Branching Model" for the forward-only +# develop invariant). + on: workflow_call: secrets: @@ -15,11 +24,21 @@ on: jobs: codegen: - name: Run codegen and pull request job + name: Run codegen and pull request job (${{ matrix.target.ref }}) runs-on: ubuntu-latest permissions: contents: write pull-requests: write + strategy: + # Each branch gets its own parallel codegen run + PR. If one + # branch's PR fails (CI, conflicts, etc.) the other is unaffected. + fail-fast: false + matrix: + target: + - ref: main + branch: codegen-main + - ref: develop + branch: codegen-develop steps: @@ -42,7 +61,7 @@ jobs: - name: Checkout code step uses: actions/checkout@v6 with: - ref: main + ref: ${{ matrix.target.ref }} token: ${{ steps.app-token.outputs.token }} - name: Run codegen step @@ -65,8 +84,8 @@ jobs: with: # App token: triggers pull_request workflow events directly, creates verified commits as the app token: ${{ steps.app-token.outputs.token }} - base: main - branch: codegen + base: ${{ matrix.target.ref }} + branch: ${{ matrix.target.branch }} title: 'Update codegen files' body: 'This PR updates the codegen files.' commit-message: 'Update codegen files' diff --git a/AGENTS.md b/AGENTS.md index 22466044..cd737d41 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -21,7 +21,9 @@ Treat this file as authoritative for everything else; don't restate its rules el - `develop` is the integration branch. Feature branches → `develop` is **squash-only**; develop is kept linear. - `develop` → `main` is **merge-commit only** (no squash, no rebase). Merge commits preserve develop's commit list as a real second-parent reference on main, which is what makes the "release on every push" model attribute releases to the develop commits that produced them. Branch protection enforces this: the develop ruleset allows only `squash`, the main ruleset allows only `merge`. - All commits on both branches must be cryptographically signed (SSH or GPG). Squash and merge commits created via the GitHub UI are signed by GitHub's web-flow key. -- **Dependabot scheduled updates target `develop`** (see `target-branch` in [`.github/dependabot.yml`](./.github/dependabot.yml)) so develop stays ahead of main; bumps land on develop first and bundle into the next develop → main merge-commit. Security update PRs from Dependabot open against `main` directly (Dependabot doesn't honor `target-branch` for those). The merge-bot ([`.github/workflows/merge-bot-pull-request.yml`](./.github/workflows/merge-bot-pull-request.yml)) picks `--squash` or `--merge` from each PR's base ref so the form matches the ruleset on either base. +- **`develop` is forward-only — no `main → develop` back-merges.** The develop ruleset's squash-only setting physically blocks merge commits on develop. Historical back-merge commits visible in `git log` (`b9b0447`, `410ba56`, `ffb9e64`, `5ce95cf`, etc.) predate this rule and must not be repeated. +- **Bots (Dependabot and codegen) target both `main` and `develop` in parallel.** [`.github/dependabot.yml`](./.github/dependabot.yml) duplicates every ecosystem entry (one per branch) and [`.github/workflows/run-codegen-pull-request-task.yml`](./.github/workflows/run-codegen-pull-request-task.yml) runs as a matrix over both branches with branch names `codegen-main` and `codegen-develop`. Each branch absorbs its own bot PRs independently, so neither falls behind, and the forward-only rule still holds (nothing is back-merged from main to develop — both branches receive their updates directly). The merge-bot ([`.github/workflows/merge-bot-pull-request.yml`](./.github/workflows/merge-bot-pull-request.yml)) dispatches `--squash` or `--merge` from each PR's base ref via a `case` statement so the form matches the ruleset on either base. Dependabot **security** PRs (CVE-driven) always open against the repo default branch (`main`) regardless of `target-branch` — the same `case` statement covers them. +- **Why parallel dual-target rather than develop-only with eventual flow-through:** push-distribution channels (HACS for Home Assistant integrations, Linux distros that vendor from `main`, etc.) consume `main` directly. A develop-only model would leave `main` running stale code during long-running develop features. Codegen content can also be production-critical (live API-derived data, language lists, build catalogs) rather than just sample/demo content, so both branches need fresh codegen on their own cadence. ## Pull Request Title and Commit Message Conventions diff --git a/README.md b/README.md index 27475790..03de1550 100644 --- a/README.md +++ b/README.md @@ -457,9 +457,11 @@ Licensed under the [MIT License][license-link]\ - The App token is used by **both** the codegen workflow (`run-codegen-pull-request-task.yml`) **and** every job in `merge-bot-pull-request.yml`. App-authored pushes/PRs trigger downstream `pull_request` and `push` workflow events directly — unlike `GITHUB_TOKEN`-authored events, which are blocked by GitHub's recursion guard. This is why `publish-release.yml` fires on the merge commit after Dependabot or codegen auto-merge, and why the codegen workflow no longer needs the legacy close/reopen dance to trigger auto-merge. - The codegen auto-merge condition in `merge-bot-pull-request.yml` (`merge-codegen` job) requires: - `github.event.pull_request.user.login == 'ptr727-codegen[bot]'` — PR was opened by the App. - - `github.event.pull_request.head.ref == 'codegen'` — source branch is `codegen`. - - `github.event.pull_request.base.ref == 'main'` — PR targets `main`. + - `github.actor == 'ptr727-codegen[bot]'` — the event was triggered by the App (a maintainer pushing commits to the App PR won't auto-merge). - `github.event.pull_request.head.repo.full_name == github.repository` — PR is from this repo (not a fork). + - **Strict head/base pairing** — `(head.ref == 'codegen-main' && base.ref == 'main') || (head.ref == 'codegen-develop' && base.ref == 'develop')`. Codegen runs as a matrix opening one PR per branch; this pairing prevents a misconfigured workflow from sneaking a `codegen-develop` branch into `main` or vice versa. + + Codegen targets `main` AND `develop` in parallel (matrix in `run-codegen-pull-request-task.yml`), so generated content lands on both branches independently without any back-merging. See [AGENTS.md "Branching Model"](./AGENTS.md#branching-model) for why this dual-target pattern beats develop-only-with-flow-through. **Codegen workflow schedule**: @@ -474,16 +476,22 @@ Licensed under the [MIT License][license-link]\ - [TODO:](https://github.com/orgs/community/discussions/184410): Disable merge and rebase merging, ruleset merge rules do not currently work. - `Always suggest updating pull request branches` - `Allow auto-merge` -- Rules / Rulesets: - - "Main and Develop": - - Target branches: `main`, `develop`. +- Rules / Rulesets — **separate rulesets per branch** so allowed merge methods differ (develop = squash-only; main = merge-commit-only, per AGENTS.md). Everything else is shared. + - "Develop": + - Target branches: `develop`. + - Allowed merge methods: `Squash` + - Plus shared settings (below). + - "Main": + - Target branches: `main`. + - Allowed merge methods: `Merge` + - Plus shared settings (below). + - Shared settings (apply to both rulesets): - `Restrict deletions` - - `Require linear history` + - `Require linear history` (only enforceable on `develop`; `main` carries merge commits by design) - `Require signed commits` - `Require a pull request before merging` - `Dismiss stale pull request approvals when new commits are pushed` - `Require conversation resolution before merging` - - Allowed merge methods: `Squash` - `Require status checks to pass` - `Require branches to be up to date before merging` - Status checks that are required: `Check pull request workflow status` @@ -496,15 +504,40 @@ Licensed under the [MIT License][license-link]\ ### Template - Branching Workflow -- Create persistent `main` and `develop` branches. -- Protect `main` and `develop` branches with branch protection rules. -- Make sure that `main` and `develop` are always building error free. -- Create feature branches from the `develop` branch. -- Only commit to feature branches, do not commit directly to `develop` or to `main`. -- Always "Squash and merge" from feature branches to the `develop` branch to minimize change history. -- Always "Squash and merge" from `develop` to `main` to maintain a linear history. -- Bot generated pull requests (codegen, dependabot) always checkout from and merge into `main` directly. -- If `develop` falls behind after a bot merge, re-run codegen or rebase `develop` on `main` before merging `develop` to `main`. +See [AGENTS.md "Branching Model"](./AGENTS.md#branching-model) for the authoritative definition. Summary: + +- Persistent `main` and `develop` branches, each with its own ruleset (above). Both must always be building error free. +- Feature branches off `develop`. Only commit on feature branches, never directly to `develop` or `main`. +- Feature → `develop`: **squash-merge** (develop ruleset enforces this; develop is kept linear). +- `develop` → `main`: **merge-commit** (preserves develop's commit list as a real second-parent reference on main; main ruleset enforces this). +- **`develop` is forward-only.** No `main → develop` back-merges. The develop squash-only ruleset physically blocks merge commits. +- **Bots open parallel PRs against both branches.** [`.github/dependabot.yml`](./.github/dependabot.yml) duplicates each ecosystem entry per branch, and [`.github/workflows/run-codegen-pull-request-task.yml`](./.github/workflows/run-codegen-pull-request-task.yml) runs as a matrix (branch names `codegen-main` and `codegen-develop`). Each branch absorbs its own bot PRs independently — neither falls behind, no back-merges needed. + +### Template - Release Distribution Model: Push vs. Pull + +This template ships with a **push-on-merge release model** — every commit on `main` triggers [`.github/workflows/publish-release.yml`](./.github/workflows/publish-release.yml) which publishes a GitHub release, NuGet/PyPI uploads, Docker tags, and platform executables. With the dual-target bot model (Dependabot/codegen targeting both branches), this means every Dependabot bump that lands on `main` produces a new release. That's the right default for projects whose consumers **pull** at their own cadence (Docker pulls, NuGet/PyPI installs, manual binary downloads) — releases are cheap and frequent, consumers update on their own schedule. + +For projects whose consumers are **pushed** updates (HACS for Home Assistant, package managers that auto-update integrations, Linux distros that vendor from `main`), every release is a forced update to all users. Frequent bot-driven releases become noise. To switch to a **manual main-release model** while keeping the rest of the dual-target dual-channel flow: + +1. Edit [`.github/workflows/publish-release.yml`](./.github/workflows/publish-release.yml) and change the trigger: + + ```diff + on: + - push: + - branches: [ main, develop ] + - workflow_dispatch: + + push: + + branches: [ develop ] + + workflow_dispatch: + ``` + + Result: `develop` pushes still publish dev releases automatically (PEP 440 `.dev0` to PyPI, NBGV-prerelease tags on NuGet, prerelease GitHub releases). `main` pushes no longer auto-publish; you trigger the release manually via the GitHub Actions UI (`workflow_dispatch`) when a real release is wanted. + +2. **(Optional)** narrow what flows into `main` automatically. If a sea of Dependabot PRs on `main` is noisy without auto-release, either: + - Drop the `main`-target Dependabot entries from `.github/dependabot.yml` (so deps update on `develop` only, and reach `main` through the next develop → main release the maintainer triggers — closer to a pure develop-only flow with manual cadence), or + - Keep dual-target Dependabot and let the merge-bot auto-merge them silently into `main`; main always has fresh code, but ships only when the maintainer dispatches a release. + +For an example of the manual-release model in production, see [homeassistant-purpleair](https://github.com/ptr727/homeassistant-purpleair) — that integration ships through HACS (push distribution) and uses `workflow_dispatch` for actual releases. From 6458380e1ca73e8c22d0ceb1d8a647913eb55bc9 Mon Sep 17 00:00:00 2001 From: Pieter Viljoen Date: Mon, 11 May 2026 16:27:49 -0700 Subject: [PATCH 2/3] Address Copilot review on PR #78: job suffix, linear history, merge enable MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Three fair findings from Copilot: 1. The matrix-job `name:` ended in the matrix ref (`(main)` / `(develop)`) instead of the required "job" suffix from AGENTS.md. Reorder to put the matrix ref before "job": `Run ${{ matrix.target.ref }} codegen and pull request job`. 2. README rulesets section had `Require linear history` in the "Shared settings" block while also noting it only applies to develop. That's contradictory and could mislead adopters into enabling it on main (where it would block merge commits). Move the setting into the Develop-only ruleset entry. 3. README repo-level Pull Requests settings listed only `Allow squash merging`, but the main ruleset is merge-commit-only — so "Allow merge commits" must also be enabled at the repo level (rulesets pick from what the repo permits). Rewrote that block to enable both squash and merge, dropped the stale TODO about disabling merge/rebase (which contradicts the actual config). Co-Authored-By: Claude Opus 4.7 (1M context) --- .github/workflows/run-codegen-pull-request-task.yml | 2 +- README.md | 9 +++++---- 2 files changed, 6 insertions(+), 5 deletions(-) diff --git a/.github/workflows/run-codegen-pull-request-task.yml b/.github/workflows/run-codegen-pull-request-task.yml index 4b33a9f0..4e284c34 100644 --- a/.github/workflows/run-codegen-pull-request-task.yml +++ b/.github/workflows/run-codegen-pull-request-task.yml @@ -24,7 +24,7 @@ on: jobs: codegen: - name: Run codegen and pull request job (${{ matrix.target.ref }}) + name: Run ${{ matrix.target.ref }} codegen and pull request job runs-on: ubuntu-latest permissions: contents: write diff --git a/README.md b/README.md index 03de1550..865b1066 100644 --- a/README.md +++ b/README.md @@ -471,15 +471,17 @@ Licensed under the [MIT License][license-link]\ - General: - Default branch: `main` - - Pull requests: - - `Allow squash merging` - - [TODO:](https://github.com/orgs/community/discussions/184410): Disable merge and rebase merging, ruleset merge rules do not currently work. + - Pull requests — **both** merge methods enabled at the repo level so each branch ruleset can pick the right one (develop = `Squash`, main = `Merge`): + - `Allow merge commits` ✓ (required for develop → main releases) + - `Allow squash merging` ✓ (required for feature → develop merges) + - `Allow rebase merging` — disabled (no flow uses it; the develop ruleset forbids it anyway) - `Always suggest updating pull request branches` - `Allow auto-merge` - Rules / Rulesets — **separate rulesets per branch** so allowed merge methods differ (develop = squash-only; main = merge-commit-only, per AGENTS.md). Everything else is shared. - "Develop": - Target branches: `develop`. - Allowed merge methods: `Squash` + - `Require linear history` (develop is kept linear; main carries merge commits by design, so this setting belongs to develop only) - Plus shared settings (below). - "Main": - Target branches: `main`. @@ -487,7 +489,6 @@ Licensed under the [MIT License][license-link]\ - Plus shared settings (below). - Shared settings (apply to both rulesets): - `Restrict deletions` - - `Require linear history` (only enforceable on `develop`; `main` carries merge commits by design) - `Require signed commits` - `Require a pull request before merging` - `Dismiss stale pull request approvals when new commits are pushed` From 0664f09889db54f6df4603cfbf42ffdfd270e4b5 Mon Sep 17 00:00:00 2001 From: Pieter Viljoen Date: Mon, 11 May 2026 16:34:26 -0700 Subject: [PATCH 3/3] Soften actor-check claim: doesn't disable already-enabled auto-merge MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Copilot review on 6458380 caught that the prior wording claimed the github.actor check stops maintainer-pushed commits from auto-merging. That's only half right: it stops the *job* from re-enabling auto-merge, but it doesn't disable auto-merge that's already active from the initial bot-driven `opened` event. So once auto-merge is on, any commit that passes CI will land — including a maintainer's. Rewrite both the workflow comment and the README auto-merge condition to spell this out accurately, with the maintainer-edit workaround (`gh pr merge --disable-auto ` before pushing). A real safeguard job (a `synchronize`-triggered `disable-auto` for non-bot actors) is a worthwhile follow-up but is out of scope for this PR. Co-Authored-By: Claude Opus 4.7 (1M context) --- .github/workflows/merge-bot-pull-request.yml | 15 +++++++++++---- README.md | 2 +- 2 files changed, 12 insertions(+), 5 deletions(-) diff --git a/.github/workflows/merge-bot-pull-request.yml b/.github/workflows/merge-bot-pull-request.yml index 5e012547..7aaaf7c2 100644 --- a/.github/workflows/merge-bot-pull-request.yml +++ b/.github/workflows/merge-bot-pull-request.yml @@ -94,10 +94,17 @@ jobs: # for example, sneak a `codegen-develop` branch into `main`. # Both the PR author AND the event actor must be the App: the # author check stops human-opened PRs that happen to target a - # `codegen-*` branch from auto-merging; the actor check stops a - # maintainer pushing extra commits to the App's codegen branch - # (a `synchronize` event the human triggered) from auto-merging - # unintended changes through the App PR. + # `codegen-*` branch from auto-merging; the actor check stops + # this job from re-invoking `gh pr merge --auto` on a + # `synchronize` event a maintainer triggered. + # + # Limitation worth knowing: the actor check does NOT disable + # auto-merge if it was already enabled by the initial bot-driven + # `opened` event. Once auto-merge is on, every commit that + # passes CI will land — including a maintainer's. To edit a + # codegen PR safely, run `gh pr merge --disable-auto ` (or + # click "Disable auto-merge" in the GitHub UI) BEFORE pushing, + # then re-enable it manually when ready. if: >- github.event.pull_request.user.login == 'ptr727-codegen[bot]' && github.actor == 'ptr727-codegen[bot]' && diff --git a/README.md b/README.md index 865b1066..9c5e6584 100644 --- a/README.md +++ b/README.md @@ -457,7 +457,7 @@ Licensed under the [MIT License][license-link]\ - The App token is used by **both** the codegen workflow (`run-codegen-pull-request-task.yml`) **and** every job in `merge-bot-pull-request.yml`. App-authored pushes/PRs trigger downstream `pull_request` and `push` workflow events directly — unlike `GITHUB_TOKEN`-authored events, which are blocked by GitHub's recursion guard. This is why `publish-release.yml` fires on the merge commit after Dependabot or codegen auto-merge, and why the codegen workflow no longer needs the legacy close/reopen dance to trigger auto-merge. - The codegen auto-merge condition in `merge-bot-pull-request.yml` (`merge-codegen` job) requires: - `github.event.pull_request.user.login == 'ptr727-codegen[bot]'` — PR was opened by the App. - - `github.actor == 'ptr727-codegen[bot]'` — the event was triggered by the App (a maintainer pushing commits to the App PR won't auto-merge). + - `github.actor == 'ptr727-codegen[bot]'` — the event was triggered by the App. This stops the job from **re-invoking `gh pr merge --auto`** on a maintainer-triggered `synchronize`, but **does not disable auto-merge once it was already enabled by the initial bot-driven `opened` event**. If a maintainer pushes commits to a codegen PR with auto-merge already on, the next CI pass will merge them. To edit a codegen PR safely, disable auto-merge first via `gh pr merge --disable-auto ` (or the GitHub UI button) before pushing. - `github.event.pull_request.head.repo.full_name == github.repository` — PR is from this repo (not a fork). - **Strict head/base pairing** — `(head.ref == 'codegen-main' && base.ref == 'main') || (head.ref == 'codegen-develop' && base.ref == 'develop')`. Codegen runs as a matrix opening one PR per branch; this pairing prevents a misconfigured workflow from sneaking a `codegen-develop` branch into `main` or vice versa.