1- # Auto-merge — native GitHub auto-merge via GITHUB_TOKEN. No custom App, no
2- # AUTOMERGE_* secrets, no reusable workflow. The 2026 pattern.
1+ # Auto-merge — enables GitHub native auto-merge (squash) via GITHUB_TOKEN.
32#
43# Synced from github-settings-automation/templates/auto-merge.yml by the
54# weekly enforce-repo-settings sweep. Do not hand-edit per-repo.
65#
7- # Tier separation (this file vs. github-settings-automation/pr-heal.yml):
6+ # Event-driven and per-repo: enables native auto-merge only for trusted
7+ # automation branches (prefix policy below). Renovate PRs don't need this
8+ # workflow — the shared preset (github>ANcpLua/github-settings-automation)
9+ # sets `platformAutomerge: true`.
810#
9- # - THIS FILE (per- repo, event-driven):
10- # Fires immediately on PR open/sync/review-submit. Handles BOT and
11- # AI-AGENT PRs that should auto-merge with no human latency, plus
12- # CodeRabbit-approved manual PRs .
11+ # Prereqs (one-time per repo): "Allow auto-merge" enabled in repo settings,
12+ # and branch protection on `main` listing the required status checks native
13+ # auto-merge waits for. enforce-repo-settings.yml flips allow_auto_merge
14+ # automatically across the fleet .
1315#
14- # - pr-heal.yml Job 0 (central cron, 15-min sweep):
15- # Handles OWNER-authored PRs (incl. agents acting via gh CLI auth as
16- # ANcpLua) with a 5-min cooldown so reviewer tiers (CodeRabbit, Codex,
17- # Claude PR review) land their advisory comments before merge-on-green
18- # fires. See PR#170 incident (2026-05-18) for the precedent: owner PR
19- # merged before CodeRabbit could submit its 5 actionable comments.
16+ # delete-merged-branch: repo-level delete_branch_on_merge reliably fires for
17+ # user-initiated merges but not for merges landed by the github-actions app
18+ # via native auto-merge (observed fleet-wide, e.g. qyl #452–#456, #477–#479
19+ # survived; user-merged #476 was deleted). This job closes that gap for the
20+ # automation prefixes. HEAD_REF is passed via env, never interpolated into
21+ # the script, so a crafted branch name cannot inject into the shell under
22+ # pull_request_target's write token.
2023#
21- # The owner-clause that USED to live in this file's `if:` was removed in
22- # the same change. Routing owner PRs through the cron tier gives them the
23- # cooldown they need without blocking the bot tier.
24+ # Branch-prefix policy:
25+ # - claude/ — Claude Code agents
2426#
25- # Renovate bot PRs do not need this workflow: Renovate enables native
26- # auto-merge itself via `platformAutomerge: true` in the shared preset
27- # (github>ANcpLua/renovate-config).
28- #
29- # Prereqs (one-time per repo): Settings → General → "Allow auto-merge"
30- # enabled. Branch protection on `main` lists the required status checks;
31- # native auto-merge waits for them. enforce-repo-settings.yml flips
32- # allow_auto_merge automatically across the fleet.
33- #
34- # Background: the AUTOMERGE_APP_ID GitHub App was deleted on 2026-05-12
35- # as an antipattern (single point of failure across N repos, maintenance
36- # overhead, and Renovate's platformAutomerge already does the bot tier
37- # natively without it).
27+ # Both jobs require head.repo.full_name == github.repository. A branch prefix
28+ # is chosen by whoever opens the PR, so under pull_request_target — which runs
29+ # with the base repo's write token even for fork PRs — the prefix alone gates
30+ # nothing. Without the same-repo check any fork can push a `claude/…` branch
31+ # and land it on main unreviewed (ancplua-claude-plugins#345, merged 10s after
32+ # open by an outside contributor while the policy trusted `codex/`).
3833
3934name : Auto-merge
4035
4136on :
4237 pull_request_target :
43- types : [opened, synchronize, reopened, ready_for_review]
44- pull_request_review :
45- types : [submitted]
38+ types : [opened, synchronize, reopened, ready_for_review, closed]
4639
4740permissions :
4841 contents : write
@@ -52,16 +45,28 @@ jobs:
5245 enable-auto-merge :
5346 runs-on : ubuntu-latest
5447 if : |
55- github.event.pull_request.draft == false && (
56- startsWith(github.event.pull_request.head.ref, 'claude/') ||
57- startsWith(github.event.pull_request.head.ref, 'copilot/') ||
58- startsWith(github.event.pull_request.head.ref, 'jules/') ||
59- (github.event_name == 'pull_request_review'
60- && github.event.review.state == 'approved'
61- && github.event.review.user.login == 'coderabbitai[bot]')
62- )
48+ github.event.action != 'closed' &&
49+ github.event.pull_request.draft == false &&
50+ github.event.pull_request.head.repo.full_name == github.repository &&
51+ startsWith(github.event.pull_request.head.ref, 'claude/')
6352 steps :
6453 - name : Enable native auto-merge (squash)
6554 env :
6655 GH_TOKEN : ${{ secrets.GITHUB_TOKEN }}
6756 run : gh pr merge ${{ github.event.pull_request.number }} --auto --squash --repo ${{ github.repository }}
57+
58+ delete-merged-branch :
59+ runs-on : ubuntu-latest
60+ if : |
61+ github.event.action == 'closed' &&
62+ github.event.pull_request.merged == true &&
63+ github.event.pull_request.head.repo.full_name == github.repository &&
64+ startsWith(github.event.pull_request.head.ref, 'claude/')
65+ steps :
66+ - name : Delete merged head branch
67+ env :
68+ GH_TOKEN : ${{ secrets.GITHUB_TOKEN }}
69+ HEAD_REF : ${{ github.event.pull_request.head.ref }}
70+ run : |
71+ gh api -X DELETE "repos/${{ github.repository }}/git/refs/heads/$HEAD_REF" \
72+ || echo "head branch already deleted"
0 commit comments