Self-hosted service
palantir/bulldozer avatar
palantir/bulldozer

bulldozer: a GitHub App that merges pull requests when the rules say so

GitHub Pull Request Auto-Merge Bot

811 stars112 forksGoApache-2.0

At a glance

What is it?
bulldozer is a Go GitHub App that merges a pull request once every required status check has passed and the required reviews are in, and only for pull requests that match a configured trigger. It is a GitHub-native alternative to a merge queue, with a per-repository YAML policy that can be shared across an organization.
Who is it for?
Adopt bulldozer if your pull requests sit reviewed and green for a long time because CI is slow, and you want merging to happen as soon as the conditions are met rather than when a human gets to the button. Do not adopt it as a replacement for merge queues with ordering requirements, since it merges each qualifying pull request independently the moment its own conditions are satisfied.
Can I use it commercially?
Yes. Apache-2.0 is a permissive licence: you can use, modify and sell software built on it, as long as you keep its copyright and licence notices.
Is it still maintained?
Yes. The repository received new commits within the last day.
What is it written in?
Mainly Go, according to GitHub's language statistics.

Answers come from the project's GitHub data, last synced on October 23, 2026, and from our analysis. They are not legal advice.

Editorial analysis

A bot that only merges what GitHub already allows

bulldozer is a GitHub App that automatically merges pull requests when, and only when, all required status checks are successful and required reviews are provided. The parenthetical is the design.

The README states the guarantee plainly: bulldozer will only merge pull requests that GitHub allows non-admin collaborators to merge. All branch protection settings, including required status checks and required reviews, are respected. In practice the bot has no more authority over a repository than an outside contributor with merge rights, and it uses that authority only after the checks a human would have to wait for have passed.

The capabilities around that core are listed separately. It can merge only pull requests that match certain conditions, such as having a specific label or comment. It can ignore pull requests that match certain conditions. It can automatically keep pull request branches up to date by merging in the target branch. And it can wait for additional status checks that GitHub does not require.

Those four extras are what make it a policy tool rather than a plain auto-merge switch, and the third of them is the one that changes daily work most, because a pull request that is behind the target branch and therefore unmergeable is the classic reason a green pull request sits there.

The implementation is Go, with the module github.com/palantir/bulldozer built against Go 1.27 with the go1.27.2 toolchain. The dependency list tells you what it talks to, with google/go-github v92 for the REST API, palantir/go-githubapp for the GitHub App plumbing, palantir/go-baseapp for the server framework, cobra for the command line, zerolog for logging, go-metrics for instrumentation and yaml.v2 for the configuration files.

Branch protection is the boundary that matters

The README is unusually direct about the precondition here, and it is the single most important thing to understand before installing the bot.

It says you must enable branch protection to prevent bulldozer from immediately merging every pull request. Without required status checks and required reviews configured on the branch, there is nothing for the bot to wait for, and a bot that merges on sight is not what anybody wants.

That ordering also explains the security model. bulldozer runs as an installation of a GitHub App, so it is granted access only to the repositories you install it on, and its merge action is bounded by the same rules GitHub applies to a collaborator. There is no separate bulldozer permission model to reason about, which is a genuine simplification compared with a bot that holds an admin token.

The other behavioural property worth planning around is that bulldozer is event-driven. The README says it will usually merge a pull request within a few seconds of the pull request satisfying all preconditions. So the merge happens at the moment the last required check reports success, not on a poll cycle, and any policy you write has to be correct at the instant it is evaluated rather than at a review checkpoint.

If your process depends on a human pressing a button as a deliberate final act, that assumption has to go. The README lists its intended users: teams whose CI builds take longer than the normal review process, teams pairing it with policy-bot to auto merge pre-approved or automated changes, teams that want to give contributors more control over when they can merge without granting write access, and teams with enough active development that keeping a pull request up to date with the target branch is a chore in itself.

The trigger and ignore sets in .bulldozer.yml

The behaviour is configured by a .bulldozer.yml file at the root of the repository, with the file name and location configurable when you run your own instance of the server.

The shape of the file is this:

yaml
version: 1
merge:
  trigger:
    labels: ["merge when ready"]
    comment_substrings: ["==MERGE_WHEN_READY=="]

The trigger section defines the set of pull requests considered by bulldozer, and if it is missing bulldozer considers all pull requests not excluded by the ignore conditions. The ignore section takes the same keys and defines the set of pull requests bulldozer ignores, and if that section is missing bulldozer considers all pull requests.

The available keys are more than labels. A pull request can be triggered by a label, matched case-insensitively, by a comment substring appearing in the body or in any comment, by an exact comment match, by a substring in the pull request body, by the target branch name, by a regular expression matching the target branch, or by auto merge being enabled on the pull request.

That last one is notable. Native GitHub auto merge is not ignored by bulldozer, it is a trigger condition, so a contributor can use the interface they already know and bulldozer takes over the rest.

The ignore section uses the same vocabulary, so a do not merge label or a ==DO_NOT_MERGE== comment anywhere in the discussion is enough to take a pull request out of consideration.

Merge methods, and the deprecation you will meet in old files

The merge method is set with the method key, and the available options are merge, rebase, squash and ff-only:

yaml
  ignore:
    labels: ["do not merge"]
    comment_substrings: ["==DO_NOT_MERGE=="]
  method: squash

There is a second mechanism with a confusing history. The branch_method key allows the merge method used when auto-merging to differ per target branch, with the target branch name as the key, and it falls back to the method key when no branch matches. The README marks it as DEPRECATED in favour of merge_method, in comments in the configuration file itself.

The replacement is merge_method, which takes a list rather than a map and changes the matching semantics in a way that matters. The comment is explicit: the first method where ALL triggers match will be used, otherwise the method specified previously in merge.method is used. The documentation underlines the difference, since in merge/trigger any single match triggers bulldozer, while in merge_method all of the listed criteria must match. It also overrides any branch_method logic if one of the methods is triggered, and it is ignored entirely if no trigger criteria is provided.

So the deprecated key and its replacement are not equivalent. branch_method is a per-branch mapping, and merge_method is a list of conditional rules with a conjunction semantics. A repository carrying a branch_method block from an older configuration is therefore a candidate for a behaviour change on upgrade, which is worth checking in a pull request that touches .bulldozer.yml.

Where the configuration is read from

The lookup order for policy is one of the better designed parts of the project, and it explains how the bot can be enabled across an organisation without a migration.

The file is read from the most recent commit on the target branch of each pull request. That detail has consequences: the policy in force for a pull request is the policy on the branch it wants to merge into, not the policy in the pull request's own branch. A contributor therefore cannot change how their pull request is merged by editing a configuration file in their branch, which is the right property for a policy file.

The file may also contain a reference to a configuration in a different repository, the remote configuration feature. If the file does not exist in the repository, bulldozer tries to load a shared bulldozer.yml file at the root of the .github repository in the same organization, and both that path and the repository name are configurable when running your own instance.

A third option is a global default configuration defined when running your own instance, used when neither the repository nor the shared organization repository define one.

The final fallback is the important one. If configuration does not exist in the repository or in the shared organization repository and the server has no default configuration, bulldozer does not act on the pull request. The README draws the conclusion itself: this means it is safe to enable bulldozer on all repositories in an organization, because a repository without configuration is simply ignored.

Running your own instance

The project is usable as a hosted app and as something you run, and the differences are all in the configuration points just described.

The repository layout separates the concerns. The bulldozer/ directory holds the app logic, cmd/ holds the command line entry points with cobra driving them, main.go is the program entry point, server/ is the HTTP service, pull/ contains the pull request handling, config/ holds configuration types, docker/ holds the container build, and version/ the version information. There is a .bulldozer.yml at the repository root as the project's own policy, alongside .changelog.yml and .excavator.yml for the release tooling, and a .palantir/ directory. Dependencies are vendored in the tree, and the build uses the godel wrapper with the godelw script.

The container image is published as palantirtechnologies/bulldozer on Docker Hub, and the README carries an autorelease badge, which is Palantir's automated release pipeline marking that a release was produced without human intervention.

The recent release history shows the cadence: v1.19.3 dated 2025-06-26, v1.19.2 dated 2024-11-12 and v1.19.1 dated 2024-10-30, with the default branch named develop. The last push to the repository was on 2026-10-19, so the code is being worked on even though the tagged releases are older.

The licence is Apache-2.0, with the text in the LICENSE file at the repository root, which is the permissive choice for software you are likely to want to fork and self-host with your own defaults.

Where bulldozer is the wrong tool

Three cases point elsewhere.

The first is a merge queue requirement. bulldozer merges each qualifying pull request as soon as its own conditions are satisfied, and it does that independently of other pull requests. If your process needs pull requests merged in order, or needs to re-test a pull request against the combined result of several others, then merging the moment the last check goes green is not the same thing, and a queue is the tool for that job.

The second is a requirement for a human to be the last actor. Some teams want the merge to be an explicit decision, and a bot that merges seconds after the checks turn green removes that step by design. The README is honest about this being a workflow choice rather than a safety feature.

The third is a repository where branch protection is not in place. The bot's correctness depends entirely on required checks and required reviews existing, and it is not the tool's job to create them. Enabling it on a repository without protection merges everything, which is why the README makes the precondition explicit rather than assuming it.

There is one operational subtlety worth planning for as well. Because the policy is read from the target branch, a repository that wants a stricter rule has to land that rule on the branch first, and the very first pull request after a configuration change is evaluated under the old policy. That is the correct behaviour for safety, but it means configuration changes take effect one merge later than you might expect.

Editorial conclusion

Adopt bulldozer if your pull requests sit reviewed and green for a long time because CI is slow, and you want merging to happen as soon as the conditions are met rather than when a human gets to the button. Do not adopt it as a replacement for merge queues with ordering requirements, since it merges each qualifying pull request independently the moment its own conditions are satisfied. Verify three things first: that branch protection is enabled on the target branches, because without it the README warns bulldozer would merge every pull request immediately; which trigger you use, since the trigger set matches if any condition matches while merge_method requires all of them; and where the configuration comes from, because the file is read from the target branch of each pull request, falls back to a shared .github repository, and is otherwise inert, which is what makes it safe to enable organisation-wide. The licence is Apache-2.0, the newest release is v1.19.3 dated 2025-06-26, and the last push was on 2026-10-19.

Frequently asked questions

What does the bulldozer GitHub App do?

It automatically merges pull requests when, and only when, all required status checks are successful and required reviews are provided. It respects GitHub branch protection, and it can also restrict merging to pull requests matching a trigger, ignore pull requests matching an ignore set, keep branches up to date, and wait for extra status checks.

Why does bulldozer need branch protection enabled?

Because the bot only merges pull requests that GitHub allows non-admin collaborators to merge, which means it depends on required status checks and required reviews being configured. The README states you must enable branch protection to prevent bulldozer from immediately merging every pull request.

Where does bulldozer read its configuration from?

From the .bulldozer.yml file at the root of the repository, read from the most recent commit on the target branch of each pull request. If that file is absent it falls back to a shared bulldozer.yml in the .github repository of the same organization, then to a server default, and if none of those exist the bot does not act on the pull request.

What is the difference between merge.trigger and merge_method in bulldozer?

The trigger set matches if any single condition matches, while merge_method requires all of the listed triggers to match for the first method in the list to apply. merge_method also overrides the deprecated branch_method logic and is ignored if no trigger criteria is provided.

Which merge methods can bulldozer use?

The method key accepts merge, rebase, squash and ff-only. A branch_method key allowed a different method per target branch but is marked deprecated in favour of merge_method.

Official sources

  1. Issues
  2. License: Apache-2.0
  3. palantir/bulldozer on GitHub
  4. README
  5. Releases
Add this badge to your README

If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.

Add this badge to your README

markdown
[![Hysen Labs](https://hysenlabs.com/badge/palantir-bulldozer.svg)](https://hysenlabs.com/projects/palantir-bulldozer)

Was this useful?