Self-hosted service
palantir/policy-bot avatar
palantir/policy-bot

policy-bot: approval policies as a status check on your pull requests

A GitHub App that enforces approval policies on pull requests

1,062 stars160 forksGoApache-2.0

At a glance

What is it?
policy-bot is a Palantir GitHub App that enforces approval policies on pull requests by creating a status check you can mark as required, going past GitHub's native required reviews with rules for specific users, teams, files, authors and branches. Its behaviour is configured per repository in a .policy.yml file, and the interesting part is the four-state rule model and the predicates versus requirements distinction.
Who is it for?
policy-bot suits an organisation that has outgrown GitHub's required reviews and needs approval logic that depends on which files changed, who wrote them, or which team has to sign off, because a policy file committed to the repository is reviewable in the same pull request as the code it governs.
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 last received commits 3 days ago.
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 21, 2026, and from our analysis. They are not legal advice.

Editorial analysis

A status check is the enforcement point, not a webhook

policy-bot is a GitHub App for enforcing approval policies on pull requests, and the mechanism is the first thing to understand. It does this by creating a status check, which can be configured as a required status check.

That choice has consequences. GitHub natively supports required reviews, and policy-bot exists because it offers more complex approval features. The README lists them: require reviews from specific users, organizations, or teams, apply rules based on the files, authors, or branches involved in a pull request, combine multiple approval rules with and and or conditions, and automatically approve pull requests that meet specific conditions.

By running through a status check, policy-bot inherits GitHub's own merge gate. A branch protection rule that requires the policy-bot check cannot be merged without it passing, and the failure message can be as detailed as the bot wants. That is a much better integration story than a bot that comments and hopes someone reads it.

The downside of the same choice is that the check has to be wired into branch protection for each repository, and a repository with no policy at all gets no check, because if a policy does not exist in the repository or in the shared organization repository, policy-bot does not post a status check on the pull request. The README notes that this makes it safe to enable policy-bot on all repositories in an organization, which is a sensible default for a large installation.

The project is Apache-2.0 licensed, written in Go, and the default branch is develop with a Docker image published as palantirtechnologies/policy-bot.

Rules with four components and four possible states

The policy model is the substance of the project, and the README describes it precisely.

An approval policy is a set of rules that combine to determine when a pull request is approved. Rules combine using the logical operators and and or. With no operators, a policy requires that all of its rules are approved to approve the pull request.

Each rule has four components. The name is an arbitrary string used to refer to the rule in a policy, and it also appears in the details view for a pull request, with the advice to pick names that complete the sentence this pull request is approved if. Predicates mean a rule is only active when all of them are true, and if any predicate is false the rule is skipped and does not influence the policy. Options control behaviour, including the approval methods, whether pushing new changes invalidates previous approvals, and whether the rule should request reviews from users or teams. Requirements are what must be true for the rule to be approved, and they can be approvals from users with the right properties or conditions about the pull request. If a rule has no requirements, it is always approved.

After evaluation a rule is in one of four states: approved when all predicates and requirements are true, pending when all predicates are true but one or more requirements are not, skipped when one or more predicates are not true, and error when something went wrong while evaluating.

The operator semantics follow from that. approved is equivalent to true, pending and error are equivalent to false, and skipped completely removes the rule from the condition. That last rule is the important one, because a skipped rule contributes nothing, whereas a pending rule actively blocks the policy.

Predicates versus requirements, and why the project prefers predicates

The README has a section devoted to choosing between the two mechanisms, and it does not leave the choice neutral.

Use predicates to enable a rule when a pull request is in a specific state, or to skip the rule when a pull request is not in that state. Predicates are useful when you want special approval requirements like requiring extra approval for files that involve security or automatically approving dependency updates.

Use required conditions when the pull request state itself is important for approval. Conditions are useful if you want to require that a pull request has certain status checks or specific labels.

Then the recommendation. Sometimes you can achieve a desired outcome using either approach, and in that case the project prefers predicates. Policies that use predicates may define more rules, but tend to be flatter and use fewer logical operators, and with well-chosen rule names the project finds this style of policy easier to read and reason about.

That is a considered opinion rather than a technical necessity, and it is worth understanding the reasoning. Predicates turn a rule on or off, which is a boolean in the policy's structure. Requirements add a condition inside a rule that is already active. The first shape is flatter, and flatness in a security policy is worth something, because a policy that a reviewer cannot follow is a policy that will be misread at the moment it matters.

The flip side is that a skipped rule contributes nothing at all. If the condition guarding a rule is wrong, the requirement it was supposed to enforce never applies, and the policy still passes. That is the failure mode to think about when writing predicates.

The policy file, and where it is read from

Configuration is per repository, and the resolution order is worth knowing precisely because it has a security consequence.

By default, policies are defined in a .policy.yml file at the root of the repository, and the path and file name can be changed when running your own instance of the server. The file is read from the most recent commit on the target branch of each pull request.

That last point is a deliberate design decision with two sides. Reading the policy from the target branch means the policy in force is the one on the branch being merged into, not the one in the head commit, so a pull request cannot weaken the policy it is asking to be merged. The cost is that a policy change in a pull request does not take effect for that pull request, which surprises people the first time.

The file may contain a reference to a policy in a different repository, for remote policy configuration. If the file does not exist in the repository, policy-bot tries to load a shared policy.yml file at the root of the .github repository in the same organization. That path and repository name can also be changed, and the feature can be disabled by setting options.shared_repository to an empty string in the server configuration.

The shared repository is the piece to think about for a large organisation. A single policy.yml in the .github repository of the organisation becomes the default for every repository that does not have its own, which is exactly the mechanism an organisation uses to enforce a baseline without touching every repository. It is also the mechanism that makes a mistaken edit in one place affect everything.

The server administrators can set the force_shared_policy option to disable loading policies from local repositories, which is the setting to reach for when the organisation wants a single source of truth and no per-repository overrides.

Simulation, disapproval and the GitHub review interaction

Three caveats are listed in the README's table of contents, and each one changes how you should read a policy.

The first is that disapproval is disabled by default. A disapproval, as distinct from an absence of approval, is a signal that a reviewer actively does not want the change merged, and policy-bot can be configured to treat it as blocking, but the default is not to. A team that assumes a red X from a reviewer will block the merge will be wrong until they change the setting.

The second is interactions with GitHub reviews. Since policy-bot is a status check and GitHub reviews are a separate mechanism, the two can disagree. A pull request can have an approving GitHub review from someone the policy does not count, and a policy approval from someone whose GitHub review is absent. The policy is authoritative, but the human-facing signals in the GitHub UI are not, and that mismatch is a support question waiting to happen.

The third caveat covers the logical operators at a lower level. The README lists or, and, and if (Rule Predicates) as a section, which indicates that rule predicates can themselves be combined with a nested if, so a predicate is not limited to a single test. The same section on cross-organization membership tests covers the case of a rule that requires a reviewer from an organisation other than the one hosting the repository.

There is also a simulation API, listed separately from the other sections, and that is the tool for the adoption risk. Rather than installing the app on production repositories and discovering that your policy blocks a merge, you can evaluate a policy against a pull request without enforcing it. For a project about merge gates, being able to dry-run the gate is the feature that makes it safe to try.

A Go service with a webpack frontend and a godel build

The repository layout shows a service with a real web UI attached to it.

At the top level there are cmd/, config/, docker/, server/, pull/, policy/, version/ and vendor/ directories, plus main.go and go.mod. The pull/ and policy/ directories are the domain: pull request handling and policy evaluation. The vendor/ directory means dependencies are committed, which is a Palantir convention for reproducible builds.

The frontend is built with webpack, and the package.json makes the shape clear. The build script is webpack build, the production build sets NODE_ENV=production, and the dependency list is small: htmx.org at 1.9.12 is the only runtime dependency. The devDependencies are tailwindcss at 3.0, postcss, autoprefixer, cssnano, mini-css-extract-plugin, css-loader and postcss-loader, plus webpack at 5.111.1 and webpack-manifest-plugin.

So the UI is server-rendered with htmx, styled with Tailwind, and the entire front end is one small dependency. The README mentions that policy-bot also provides a UI to view the detailed approval status of any pull request, which is the piece a reviewer needs when a merge is blocked and the reason is not obvious.

The Go side is more conventional. The module is github.com/palantir/policy-bot on Go 1.27.0 with a toolchain line pinning go1.27.1, cobra for the command line, and the Palantir libraries go-baseapp and go-githubapp for the application scaffolding and the GitHub App plumbing. The GitHub API access comes through google/go-github v92, the GraphQL client through shurcooL/githubv4, and policy parsing through the vektah/gqlparser and yaml.v2. Logging is zerolog, metrics are rcrowley/go-metrics, and templatetree and hatpear handle the templating and the HTTP header parsing.

The build is driven by godel, with the godelw wrapper and a godel/ directory, which is the Palantir build toolchain. There is also a Dockerfile in the docker/ directory, and the Docker Pulls badge points at the published image.

Editorial conclusion

policy-bot suits an organisation that has outgrown GitHub's required reviews and needs approval logic that depends on which files changed, who wrote them, or which team has to sign off, because a policy file committed to the repository is reviewable in the same pull request as the code it governs. It is the wrong tool if plain required reviews are enough, since a GitHub App adds a status check, a configuration file and a deployment to maintain, and it is the wrong choice for an organisation that cannot host the app itself, since a GitHub App needs a running server. Before you adopt it, read the section on how policy-bot interacts with GitHub reviews, and note that disapproval is disabled by default, which means the tool will not block a pull request on a disapproval unless you turn that on.

Frequently asked questions

How does policy-bot enforce approval policies on pull requests?

It is a GitHub App that creates a status check on the head commit, and that status check can be configured as a required status check in GitHub's branch protection rules. If no policy exists for a repository, no status check is posted, which is why the README says it is safe to enable across an organization.

What is the difference between predicates and requirements in a policy rule?

Predicates decide whether a rule is active at all, and a rule with a false predicate is skipped and does not influence the policy. Requirements are conditions that must hold within an active rule. The README notes that sometimes either approach works and prefers predicates, since those policies tend to be flatter and use fewer logical operators.

Where does policy-bot read the policy file from?

By default from a .policy.yml file at the root of the repository, read from the most recent commit on the target branch of the pull request. If that file is absent, it falls back to a shared policy.yml at the root of the .github repository in the same organization, and that fallback can be disabled by setting options.shared_repository to an empty string.

Does a reviewer disapproval block a merge through policy-bot by default?

No. The README has a section titled Disapproval is Disabled by Default, so a disapproval does not block a pull request unless that behaviour is enabled in the policy configuration.

What is the difference between policy-bot and GitHub's native required reviews?

GitHub natively supports required reviews, and policy-bot provides more complex approval features on top: requiring reviews from specific users, organizations or teams, applying rules based on the files, authors or branches involved, combining multiple approval rules with and and or conditions, and automatically approving pull requests that meet specific conditions.

Official sources

  1. Issues
  2. License: Apache-2.0
  3. palantir/policy-bot 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-policy-bot.svg)](https://hysenlabs.com/projects/palantir-policy-bot)