A GitHub Action that lets code owners merge PRs by leaving a comment — no admin required.
It uses the standardized structure of a CODEOWNERS file to determine who has merge rights over which files.
With this file at CODEOWNERS or .github/CODEOWNERS:
README.md @your-usernameIf a PR contained only a change to README.md, this action would comment that @your-username has the ability to merge by commenting LGTM.
When that happens, the GitHub Action will merge the PR automatically.
Create a workflow file at .github/workflows/codeowners-merge.yml:
name: Codeowners merging
on:
pull_request_target: { types: [opened] }
issue_comment: { types: [created] }
pull_request_review: { types: [submitted] }
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Run Codeowners merge check
uses: elementx-ai/code-owner-self-merge@main
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}We force the use of pull_request_target as a workflow event to ensure that someone cannot change the CODEOWNER files at the same time as having that change be used to validate if they can merge.
If your CODEOWNERS file references GitHub teams (e.g. * @your-org/maintainers), the action resolves team membership at runtime via the GitHub API so that any active member of the team can authorise a merge.
This requires a token with the read:org scope. The default GITHUB_TOKEN issued to workflow runs does not have this scope, so you'll need to supply a Personal Access Token (or a GitHub App installation token with the appropriate permission) via the token input:
- name: Run Codeowners merge check
uses: elementx-ai/code-owner-self-merge@main
with:
token: ${{ secrets.CODEOWNERS_TOKEN }}If a team membership lookup fails with anything other than a 404, the action will fail the run rather than silently denying the merge — fix the token scope and retry.
Stacked PRs merge from the bottom up: merging a PR in a stack also merges every unmerged PR below it, in one atomic operation. So LGTM on a stacked PR is treated as a merge of all of those PRs:
- You need to be a code-owner of the files changed in every PR being merged, not only the one you commented on.
- Each of those PRs must be mergeable and green.
- PRs above the one you commented on are left open, and GitHub retargets them onto the stack's base branch.
Stacked PRs merge through GitHub's async merge API, which they require. If the base branch has a merge queue, the stack is added to the queue instead. Unstacked PRs keep using the classic merge endpoint.
GitHub doesn't support bypassing branch rules for stacked PRs. If your ruleset relies on the merging token being a bypass actor, for example to skip a required code owner review, a stacked PR can't be self-merged until that rule is met directly or the PR is unstacked.
Merging a PR has strict security requirements, but closing a PR or Issue can have a weaker one. Anyone with a GitHub login listed in the CODEOWNERS file has the ability to close any PR or Issue via a comment or review which includes:
@github-actions close
A closed PR can be re-opened with:
@github-actions reopen
You can set labels for specific sections of the codebase by using square brackets in CODEOWNERS entries:
packages/docs/es/**/*.md @your-username [translate] [es]Available inputs for the action:
token— GitHub token to use for API calls (falls back toGITHUB_TOKENenv var)cwd— root folder to look for CODEOWNER files inmerge_method—merge(default),squash, orrebasequiet— do not comment saying who can merge the PRownerNoPings— list of usernames to wrap in an inline code block to prevent pingingif_no_maintainers_add_label— label to apply when no CODEOWNER covers the PRif_no_maintainers_assign— space-separated@usernamelist to assign when no CODEOWNER covers the PR
- name: Run Codeowners merge check
uses: elementx-ai/code-owner-self-merge@main
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
with:
merge_method: "squash"
if_no_maintainers_add_label: "maintainers"
if_no_maintainers_assign: "@your-username"Run tests:
npm testBuild the distribution:
npm run buildThe CI workflow (build-and-test.yaml) verifies that dist/index.mjs is up to date on every PR — run npm run build before pushing if you change index.ts.
Use the GitHub UI to create a tag and release.
