Skip to content
Closed
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
68 changes: 68 additions & 0 deletions .github/workflows/auto-merge.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
name: Auto-merge

# Dual trigger:
# - `workflow_call` lets other repos call this as a reusable workflow.
# - `pull_request` auto-merges .github's own PRs using the same logic.
on:
workflow_call:
pull_request:
types: [opened, reopened, synchronize]

jobs:
enable-auto-merge:
runs-on: ubuntu-latest
steps:
- name: Generate GitHub App installation token
id: app-token
uses: actions/create-github-app-token@v3
with:
client-id: ${{ vars.AUTOMERGE_APP_CLIENT_ID }}
private-key: ${{ secrets.AUTOMERGE_APP_PRIVATE_KEY }}
# Least-privilege: only the permissions needed to enable auto-merge.
permission-contents: write
permission-pull-requests: write
- name: Enable auto-merge (merge commit)
uses: actions/github-script@v7
with:
github-token: ${{ steps.app-token.outputs.token }}
script: |
const pr = await github.rest.pulls.get({
owner: context.repo.owner,
repo: context.repo.repo,
pull_number: context.payload.pull_request.number,
});
const nodeId = pr.data.node_id;
const mutation = `
mutation EnableAutoMerge($id: ID!) {
enablePullRequestAutoMerge(input: {
pullRequestId: $id,
mergeMethod: MERGE
}) {
pullRequest { number autoMergeRequest { mergeMethod } }
}
}
`;
// Retry with exponential backoff. GitHub rejects with UNPROCESSABLE
// while a PR is still "unstable" (checks not started yet); since
// ~Mar 2026 it also rejects when all required checks already passed
// (GitHub auto-merges anyway in that case, so we treat it as success).
const maxAttempts = 6;
for (let i = 0; i < maxAttempts; i++) {
try {
await github.graphql(mutation, { id: nodeId });
console.log('Auto-merge enabled.');
break;
} catch (e) {
const msg = e.message ?? '';
const isUnstable = msg.includes('unstable') ||
(e.errors ?? []).some(err => (err.message ?? '').includes('unstable'));
const alreadyEnabled = msg.includes('already enabled') ||
(e.errors ?? []).some(err => (err.message ?? '').includes('already enabled'));
if (alreadyEnabled) { console.log('Auto-merge already enabled, skipping.'); break; }
if (isUnstable && i < maxAttempts - 1) {
const delay = Math.min(5000 * 2 ** i, 30000);
console.log(`PR unstable (attempt ${i + 1}/${maxAttempts}), retrying in ${delay}ms…`);
await new Promise(r => setTimeout(r, delay));
} else { throw e; }
}
}
Loading