DEV Community

Sashank
Sashank

Posted on

How I Built a Self-Merging PR System for Git-Gang

I wanted to build the world’s biggest open-source contributors list. A place where anyone could add their name with a pull request and become part of something massive.

Sounds simple. It is, until you realize that “anyone” could mean thousands of people. Manually reviewing and merging every single PR? No thanks.

So I built a self-merging PR system that validates, cleans, merges, and updates everything automatically. From submission to merge, the whole thing takes about fifteen seconds.

Here’s how it works.


The problem

When you open a repo to the internet, you can’t rely on manual moderation.
People fork, change whatever they want, and send PRs. Most are fine. Some will break things. A few will test your patience.

I needed a system that could:

  • Accept PRs from forks
  • Validate what was changed
  • Merge automatically if everything looked good
  • Flag or reject anythign invalid

Basically, I wanted to press zero buttons.


Figuring out the permissions

Here’s where it got tricky.
GitHub limits what workflows can do with PRs coming from forks. You can’t comment, label, or merge them directly using the default pull_request trigger.

The fix is to use pull_request_target, which runs in the context of the base repository, giving the workflow the permissions it needs.

The key part is to explicitly checkout the PR commit so you never execute untrusted code from a fork.

on:
  pull_request_target:
    types: [opened, synchronize, reopened, ready_for_review]
    branches: [master]

- name: Checkout code
  uses: actions/checkout@v4
  with:
    ref: ${{ github.event.pull_request.head.sha }}
Enter fullscreen mode Exit fullscreen mode

This setup runs securely while still giving the workflow full access.


Making the bot act like me

The default GITHUB_TOKEN works, but it’s limited.
It can’t comment on fork PRs or bypass branch protection, and all its commits show up as github-actions[bot].

That’s not what I wanted.

So I created a personal access token (PAT) with full repo and workflow permissions, then configured git to use my credentials.

env:
  GH_TOKEN: ${{ secrets.PAT_TOKEN || secrets.GITHUB_TOKEN }}

- name: Setup Git
  run: |
    git config --global user.name "Sashank Bhamidi"
    git config --global user.email "hello@sashank.wiki"
Enter fullscreen mode Exit fullscreen mode

Now every commit and comment looks like it came from me, but I’m not the one doing it.


Stopping duplicate runs

At one point, every time the workflow added or removed a label, it triggered itself again.
That meant duplicate comments and multiple runs per PR.

The fix was simple: skip when the action is related to labels.

if: github.event.action != 'labeled' && github.event.action != 'unlabeled'
Enter fullscreen mode Exit fullscreen mode

Sometimes, one clean conditional saves hours of noise.


Validating contributions

Each contributor edits a single file: ADD_YOUR_NAME.md.
Here’s what the format looks like:

- Name: Your Name
- Username: github-username
- Message: Optional message
Enter fullscreen mode Exit fullscreen mode

The workflow checks a few things before merging:

  1. The structure is still intact
  2. The name and username are properly formatted
  3. The username hasn’t already been used
  4. There’s no profanity
  5. No other files were changed

The profanity check uses the purgomalum.com API.
If the API goes down, the PR is flagged for manual review instead of being approved blindly. Always fail closed, not open.


Auto-merging PRs

Once validation passes, the workflow comments, labels, approves, and merges automatically.

gh pr merge ${{ github.event.number }} --squash --auto
Enter fullscreen mode Exit fullscreen mode

That’s it.

--auto merges the PR the moment all checks pass, and --squash keeps the commit history clean.


What happens after a merge

When a PR merges, another workflow kicks in. It:

  1. Extracts the new entry from ADD_YOUR_NAME.md
  2. Appends it to CONTRIBUTORS.md
  3. Sorts contributors alphabetically
  4. Updates the contributor count in README.md
  5. Resets the template
  6. Commits and pushes everything back

If anything fails mid-process, it rolls back automatically to a backup commit.
No human debugging. No broken files.


The result

The entire pipeline, from pull request to merge to updated list, runs in around fifteen seconds.
No reviews. No waiting. No stress.

It handles:

  • Fork permissions
  • Validation
  • Duplicate prevention
  • Profanity checks
  • Auto-merging
  • Rollbacks

It just works. Every time.


What I learned

  1. pull_request_target but you have to use it safely.
  2. PAT tokens unlock what GITHUB_TOKEN can’t.
  3. Always fail closed when user input is involved.
  4. Validation saves time later.
  5. Rollbacks are essential, not optional.

Try it yourself

You can see it live at git-gang.
Add your name, open a PR, and watch it merge itself. It takes about fifteen seconds.

Goal’s ten thousand contributors. Started with ten.
Let’s see how far it goes.

Top comments (0)