Automerge For Humans workflow (asyncapi/spec)
The Automerge For Humans workflow from asyncapi/spec, explained and optimized by Latchkey.
A
CI health: A - excellent
Point runs-on at Latchkey and get job timeouts, self-healing for flaky steps, and up to 58% lower cost, applied automatically.
What it does
This is the Automerge For Humans workflow from the asyncapi/spec repository, a real project running GitHub Actions. It is shown here with attribution under its Apache-2.0 license.
Below, Latchkey shows a faster, safer version produced by its optimization engine.
The workflow
workflow (.yml)
# This workflow is centrally managed in https://github.com/asyncapi/.github/
# Don't make changes to this file in this repo as they will be overwritten with changes made to the same file in above mentioned repo
# Purpose of this workflow is to allow people to merge PR without a need of maintainer doing it. If all checks are in place (including maintainers approval) - JUST MERGE IT!
name: Automerge For Humans
on:
pull_request_target:
types:
- labeled
- unlabeled
- synchronize
- opened
- edited
- ready_for_review
- reopened
- unlocked # zizmor: ignore[dangerous-triggers] needed if we want author to be our bot
permissions: {}
jobs:
automerge-for-humans:
name: Automerge PRs labeled with ready-to-merge
permissions:
contents: read # required for PR commit metadata reads
pull-requests: read # required to read pull request details in github-script steps
# it runs only if PR actor is not a bot, at least not a bot that we know
if: |
github.event.pull_request.draft == false &&
!contains(fromJSON('["asyncapi-bot","dependabot[bot]","dependabot-preview[bot]"]'), github.event.pull_request.user.login)
runs-on: ubuntu-latest
steps:
- name: Get PR authors
id: authors
uses: actions/github-script@f28e40c7f34bde8b3046d885e986cb6290c5673b # v7
with:
script: |
// Get paginated list of all commits in the PR
try {
const commitOpts = github.rest.pulls.listCommits.endpoint.merge({
owner: context.repo.owner,
repo: context.repo.repo,
pull_number: context.issue.number
});
const commits = await github.paginate(commitOpts);
if (commits.length === 0) {
core.setFailed('No commits found in the PR');
return '';
}
// Get unique authors from the commits list
const authors = commits.reduce((acc, commit) => {
const username = commit.author?.login || commit.commit.author?.name;
if (username && !acc[username]) {
acc[username] = {
name: commit.commit.author?.name,
email: commit.commit.author?.email,
}
}
return acc;
}, {});
return authors;
} catch (error) {
core.setFailed(error.message);
return [];
}
- name: Create commit message
id: create-commit-message
uses: actions/github-script@f28e40c7f34bde8b3046d885e986cb6290c5673b # v7
env:
AUTHORS_JSON: ${{ steps.authors.outputs.result }}
with:
script: |
const authors = JSON.parse(process.env.AUTHORS_JSON);
if (Object.keys(authors).length === 0) {
core.setFailed('No authors found in the PR');
return '';
}
// Create a string of the form "Co-authored-by: Name <email>"
// ref: https://docs.github.com/en/pull-requests/committing-changes-to-your-project/creating-and-editing-commits/creating-a-commit-with-multiple-authors
const coAuthors = Object.values(authors).map(author => {
return `Co-authored-by: ${author.name} <${author.email}>`;
}).join('\n');
core.debug(coAuthors);;
return coAuthors;
- name: Automerge PR
uses: pascalgn/automerge-action@22948e0bc22f0aa673800da838595a3e7347e584 #v0.15.6 https://github.com/pascalgn/automerge-action/releases/tag/v0.15.6
env:
GITHUB_TOKEN: "${{ secrets.GH_TOKEN }}"
MERGE_LABELS: "!do-not-merge,ready-to-merge"
MERGE_METHOD: "squash"
# Using the output of the previous step (`Co-authored-by: ...` lines) as commit description.
# Important to keep 2 empty lines as https://docs.github.com/en/pull-requests/committing-changes-to-your-project/creating-and-editing-commits/creating-a-commit-with-multiple-authors#creating-co-authored-commits-on-the-command-line mentions
MERGE_COMMIT_MESSAGE: "{pullRequest.title} (#{pullRequest.number})\n\n\n${{ fromJSON(steps.create-commit-message.outputs.result) }}"
MERGE_RETRIES: "20"
MERGE_RETRY_SLEEP: "30000"
The same workflow, on Latchkey
Removes redundant runs and caps runaway jobs. Added and changed lines are highlighted.
# This workflow is centrally managed in https://github.com/asyncapi/.github/ # Don't make changes to this file in this repo as they will be overwritten with changes made to the same file in above mentioned repo # Purpose of this workflow is to allow people to merge PR without a need of maintainer doing it. If all checks are in place (including maintainers approval) - JUST MERGE IT! name: Automerge For Humans on: pull_request_target: types: - labeled - unlabeled - synchronize - opened - edited - ready_for_review - reopened - unlocked # zizmor: ignore[dangerous-triggers] needed if we want author to be our bot permissions: {} jobs: automerge-for-humans: timeout-minutes: 30 name: Automerge PRs labeled with ready-to-merge permissions: contents: read # required for PR commit metadata reads pull-requests: read # required to read pull request details in github-script steps # it runs only if PR actor is not a bot, at least not a bot that we know if: | github.event.pull_request.draft == false && !contains(fromJSON('["asyncapi-bot","dependabot[bot]","dependabot-preview[bot]"]'), github.event.pull_request.user.login) runs-on: latchkey-small steps: - name: Get PR authors id: authors uses: actions/github-script@f28e40c7f34bde8b3046d885e986cb6290c5673b # v7 with: script: | // Get paginated list of all commits in the PR try { const commitOpts = github.rest.pulls.listCommits.endpoint.merge({ owner: context.repo.owner, repo: context.repo.repo, pull_number: context.issue.number }); const commits = await github.paginate(commitOpts); if (commits.length === 0) { core.setFailed('No commits found in the PR'); return ''; } // Get unique authors from the commits list const authors = commits.reduce((acc, commit) => { const username = commit.author?.login || commit.commit.author?.name; if (username && !acc[username]) { acc[username] = { name: commit.commit.author?.name, email: commit.commit.author?.email, } } return acc; }, {}); return authors; } catch (error) { core.setFailed(error.message); return []; } - name: Create commit message id: create-commit-message uses: actions/github-script@f28e40c7f34bde8b3046d885e986cb6290c5673b # v7 env: AUTHORS_JSON: ${{ steps.authors.outputs.result }} with: script: | const authors = JSON.parse(process.env.AUTHORS_JSON); if (Object.keys(authors).length === 0) { core.setFailed('No authors found in the PR'); return ''; } // Create a string of the form "Co-authored-by: Name <email>" // ref: https://docs.github.com/en/pull-requests/committing-changes-to-your-project/creating-and-editing-commits/creating-a-commit-with-multiple-authors const coAuthors = Object.values(authors).map(author => { return `Co-authored-by: ${author.name} <${author.email}>`; }).join('\n'); core.debug(coAuthors);; return coAuthors; - name: Automerge PR uses: pascalgn/automerge-action@22948e0bc22f0aa673800da838595a3e7347e584 #v0.15.6 https://github.com/pascalgn/automerge-action/releases/tag/v0.15.6 env: GITHUB_TOKEN: "${{ secrets.GH_TOKEN }}" MERGE_LABELS: "!do-not-merge,ready-to-merge" MERGE_METHOD: "squash" # Using the output of the previous step (`Co-authored-by: ...` lines) as commit description. # Important to keep 2 empty lines as https://docs.github.com/en/pull-requests/committing-changes-to-your-project/creating-and-editing-commits/creating-a-commit-with-multiple-authors#creating-co-authored-commits-on-the-command-line mentions MERGE_COMMIT_MESSAGE: "{pullRequest.title} (#{pullRequest.number})\n\n\n${{ fromJSON(steps.create-commit-message.outputs.result) }}" MERGE_RETRIES: "20" MERGE_RETRY_SLEEP: "30000"
What changed
- Run on Latchkey managed runners with one line (
runs-on), which apply the fixes below automatically and self-heal transient failures. This example useslatchkey-small; pick the runner size that fits the job. - Add a job timeout so a hung step cannot burn hours of runner time.
This workflow runs 1 job per trigger. On Latchkey the same minutes cost up to 58% less than GitHub-hosted, with zero queue time.