Skip to content
GitmoapdocsBeta
Guide· How do I…?

Repository rules

Overview

Repository rules are how you stop the things you do not want to happen from happening at all, rather than finding out afterwards. Branch rules decide who may move a branch and who may merge into it. Push rules decide what a push is allowed to contain — signed commits, a message format, an author-email pattern, a file-size ceiling, a named work item. Commit statuses are how an outside system reports its verdict on a commit back to Gitmoap, so a build result is visible next to the code that produced it.

Both kinds of rule can be set on a repository or on a group, and a group's rules reach every repository beneath it — including repositories in nested groups. That is what makes them worth setting once: protect main on the group that owns forty repositories and all forty are protected, including the ones created next week. Commit statuses are the exception: a status is posted against one commit in one repository, and there is nothing to configure ahead of time.

Quick start

Quick start3 steps

Protect your default branch

You’ll need Maintainer on the repository.
  1. Choose Settings, then Branch rules, then New rule.

  2. Enter main as the pattern, and pick the lowest level that may push and the lowest that may merge.

  3. Choose Create rule. It applies from the next push onwards.

Before you start

  • Maintainer on the thing you are configuring.Creating, changing or removing a branch rule or a push rule needs Maintainer on that repository, or on any group above it. Reading them needs Reporter — and on a public or internal repository, anyone who can see the repository can read its rules, because a rule that refuses a push is not a secret.
  • Owner to enforce a group's rules.Making a group's branch rule or push rules enforced, and changing or deleting ones that are, needs Owner on the group — an enforced rule is a promise that the repositories beneath cannot loosen it.
  • Developer to report a commit status, on the repository the commit lives in. Reading statuses follows the repository's visibility.
  • A token with the right scope, if you are calling the API or the CLI: branch_rule:read and branch_rule:manage, push_rule:read and push_rule:manage, commit_status:read and commit_status:write. Scopes are fixed when the token is minted, so a build agent that only reports results should carry commit_status:write and nothing else.
  • An identity for anything automated.A build agent that needs to push to a protected branch or report a status should be a service account owned by the repository or the group, not a person's personal token. Allowlists on a branch rule accept a user id or a service account id interchangeably.
  • Nothing needs to exist first.A branch rule can name a branch that has never been created, and a push rule can be set on an empty repository. Pre-configuring protection is the normal case: the rule is waiting when the branch appears.

How it works

The three rules answer three different questions

RuleQuestion it answersWhere it is checked
Branch ruleWho may move this branch, and who may merge into it?git push, creating or deleting a branch through Gitmoap, and merging a merge request
Push ruleWhat may this push contain?git push only
Commit statusWhat did an outside check say about this commit?Nothing is refused — it is a report

A branch rule never inspects a commit, and a push rule never asks who you are. They compose, and a push has to satisfy both, but not at the same moment. The branch rule is settled first, before git writes anything: if the branch you are moving is protected above your level, the push is refused and the objects are never taken. Every push rule is checked afterwards, once the objects have arrived, because each of them has to read the commits or the files they change.

One branch rule applies to a branch, and it is the most specific one

A branch rule targets a pattern rather than a name, and * matches any run of characters — including /, so release/* matches release/v2 and release/2026/q1 alike. When a branch is touched, every rule in force is matched against its name and exactly one wins:

  1. an exact match — a pattern with no * in it — beats any pattern with a *;
  2. otherwise the pattern with the longest run of literal characters before its first * wins, so release/* beats *;
  3. otherwise the longer pattern wins, and if two are still tied, the alphabetically first one.

A branch that matches no pattern is unprotected, and the ordinary access levels apply on their own: Developer to create a branch, and Developer to delete one.

The consequence people trip over: rules do not stack. If a group protects * with allowed_to_push: owner and the repository adds a rule for release/* with allowed_to_push: developer, then a push to release/v3 is checked only against the release/* rule. The stricter rule does not also apply. The one exception is an enforced group rule, and only for its own pattern.

Branch rules inherit whole; push rules inherit field by field

Both walk from the repository up through its parent group, its grandparent and so on to the root. They differ in what they carry up.

A branch rule is inherited as a unit, keyed on the exact pattern text. The nearest scope that defines a pattern owns that pattern completely — every field of it, not just the ones it set. A repository that defines main with allowed_to_push: developer replaces the group's main rule entirely, including its allow_force_push and its allowlists. Patterns that no closer scope defines are inherited as they are, so a repository ends up with the union of its own patterns and every ancestor pattern it did not override.

A push rule set is a flat bag of settings, and each setting is inherited on its own. The nearest scope that sets a field wins for that field, and everything else keeps coming from further up:

root group reject_unsigned_commits = true
child group max_file_size_mb = 10, commit_message_regex = "^(feat|fix): "
repository max_file_size_mb = 0
effective for the repository:
reject_unsigned_commits = true (from the root group)
commit_message_regex = "^(feat|fix): " (from the child group)
max_file_size_mb = 0 (the repository's own value wins — no limit)
everything else = off

Setting a switch to false, or max_file_size_mb to 0, is an override rather than an erasure — it means do not do this, whatever a parent says, and it is how a repository loosens a rule its group set. Sending null for a field is the opposite move: it drops the repository's own value and puts that field back to inheriting.

Nothing stops the walk, so an inherited pattern cannot be switched off from below — clearing it only restores inheritance from the group that set it. To loosen one, set your own pattern on the repository. It replaces the inherited pattern for that field, and a pattern that every message satisfies, such as ^, is as good as none — unless the group's push rules are enforced.

An enforced group rule can only be made stricter beneath

A group's branch rule, or a group's push rules, can be enforced. An enforced rule still reaches every repository beneath the group, but a repository or subgroup can no longer loosen it — only tighten it.

For a branch rule, the nearest rule for a pattern still wins as usual, and then every enforced group rule for that same pattern tightens it:

FieldBeneath an enforced rule
allowed_to_push, allowed_to_mergethe higher of the two levels applies
allow_force_pushallowed only if the enforced rule allows it too
require_code_owner_approvalrequired if the enforced rule requires it
push_allowlist, merge_allowlistonly an entry the enforced rule also names lets anyone past

For push rules, an enforced group's settings hold in every repository beneath it: a check it turns on — reject_unsigned_commits, require_issue_reference, require_closing_issue_reference — stays on, its max_file_size_mb is the most any repository beneath may allow, and its patterns — commit_message_regex, author_email_regex, prohibited_file_names_regex — replace any set beneath.

A change beneath that would loosen an enforced rule is refused with 422 WOULD_WEAKEN_ENFORCED_RULE, and the message names the field: a lower level, a force-push the enforced rule forbids, dropping code-owner approval, an allowlist entry it does not name, a check it turns on switched off, a larger or unlimited file size, or a different pattern. Only a group's rules can be enforced — nothing is beneath a repository to hold to them. This is the same "stricter only" model as enforced approval rules.

What a refused push looks like

Push rules are checked after the objects arrive, because each of them has to read the commits or the files they change. If anything fails, every ref in the push is put back where it was and git prints the whole list:

Push rejected due to policy violations:
1. [reject_unsigned_commits] Commit 4f2a91cd is not signed
2. [max_file_size_mb] File 'assets/demo.mov' (18.4 MB) exceeds 10 MB limit

Nothing lands partially. The push either applies whole or leaves the repository exactly as it was.

How to

See which rules apply to a repository

A repository's branch-rule listing returns the rules the repository itself declares. Ask for the inherited set instead and you get what is actually in force — the repository's own rules merged with every ancestor group's, each item labelled with the scope it came from, so you can tell at a glance which rules you can change here and which you have to go up a level to change.

Open the repository, choose Settings, then Branch rules. The list shows each pattern with its push and merge thresholds and its switches. Toggle Include inherited to fold in the rules coming from the groups above; inherited rows are tagged with the group they belong to and are read-only here — open that group to change one.

Terminalbash
gitmoap branch-rules list --repo-id <id>
curlbash
curl -X GET https://api.gitmoap.example/repositories/{repo_id}/branch-rules \
-H "Authorization: Bearer $GITMOAP_TOKEN"

Push rules read differently at the two levels, and the difference is deliberate. A repository's push rules come back resolved: every setting has a concrete value with inheritance already applied, which is the answer to what will happen to my next push. A group's push rules come back raw — only what that group sets itself, with everything else absent.

Open the repository, choose Settings, then Push rules. Each setting shows the value that will actually apply, with inheritance already resolved — to find out which group a value came from, open that group's own push rules and compare.

Terminalbash
gitmoap push-rules get-repo --repo-id <id>
curlbash
curl -X GET https://api.gitmoap.example/repositories/{repo_id}/push-rules \
-H "Authorization: Bearer $GITMOAP_TOKEN"

Read a single branch rule

Useful when you have a rule id from a listing, an audit entry or a script and want the whole rule including its allowlists.

From Settings → Branch rules, choose a pattern to open its detail panel: thresholds, switches, and the members and service accounts on each allowlist.

Terminalbash
gitmoap branch-rules get --repo-id <id> --branch-rule-id <id>
curlbash
curl -X GET https://api.gitmoap.example/repositories/{repo_id}/branch-rules/{branch_rule_id} \
-H "Authorization: Bearer $GITMOAP_TOKEN"

Protect a branch

Give the pattern and the thresholds. Everything is optional except the pattern: a rule you create with nothing else set requires Maintainer to push and Maintainer to merge, forbids force-push, does not require code-owner approval, and has empty allowlists.

allowed_to_push and allowed_to_merge are floored at Developer — guest and reporter are refused, because a rule that lets a non-member move a branch is a rule that protects nothing.

Open the repository, choose Settings, then Branch rules, then New rule. Type the branch name or a pattern such as release/*, pick the minimum level for pushing and for merging, and set the switches you want. Create adds the rule immediately; it applies to the next push, not retroactively.

Terminalbash
gitmoap branch-rules create --repo-id <id> --branch-pattern <branch pattern>
curlbash
curl -X POST https://api.gitmoap.example/repositories/{repo_id}/branch-rules \
-H "Authorization: Bearer $GITMOAP_TOKEN" \
-H "Content-Type: application/json" \
-d '{ "branch_pattern": "<string>" }'

The two thresholds are independent and are checked on different actions. allowed_to_push governs moving the branch directly — git push, and creating or deleting the branch through Gitmoap. allowed_to_merge governs landing a merge request that targets the branch, and merging never consults allowed_to_push. Setting allowed_to_push: owner with allowed_to_merge: developer is the common shape: nobody pushes to main by hand, but any Developer can land a reviewed merge request into it.

allow_force_push is a property of the rule and not of your level. Left off, a non-fast-forward push to a matching branch is refused for everyone, Owners included.

require_code_owner_approval turns into a check on the merge request rather than on the push: a merge into a matching branch waits for approval from whoever owns the changed files, whether that ownership comes from a CODEOWNERS file or from the service catalog. If nothing claims a changed file, the check fails rather than passing quietly — a rule that requires an owner and finds none has not been satisfied.

Change who can push or merge to a protected branch

Every field of a rule can be changed except its pattern. The pattern is the rule's identity, so moving protection from release/* to releases/* means creating the new rule and deleting the old one.

Open the rule from Settings → Branch rules and edit it in place. Save applies the change to the next push; work already pushed is unaffected.

Terminalbash
gitmoap branch-rules update --repo-id <id> --branch-rule-id <id>
curlbash
curl -X PATCH https://api.gitmoap.example/repositories/{repo_id}/branch-rules/{branch_rule_id} \
-H "Authorization: Bearer $GITMOAP_TOKEN"

To let one build agent past a threshold without promoting it, add its id to push_allowlist or merge_allowlist. A team's id works too, and then everyone on the team is let past. A rule permits an action when the caller's level is high enough or the caller — or a team they are on — is on the matching allowlist — so a service account sitting at Developer can push to a branch that requires Owner, and nothing else about it changes. The two lists are independent: being on the push allowlist grants no merge permission. And an allowlist only lifts a caller past the rule; the ordinary level for the action is checked first and still applies, so an account below Developer can neither push to a branch nor delete one, however the allowlists read.

Sending an allowlist replaces it entirely rather than adding to it. Send the full list you want, and send [] to clear one. Each list holds up to 50 entries, they must be distinct, and every entry has to be a real user, service account or team of your organisation — an id that resolves to none of them is refused rather than stored. Deleting a team removes it from every allowlist.

Remove a branch rule

Warning

Deleting a branch rule removes protection from every branch its pattern matched, immediately and without a confirmation step beyond the one in the UI. There is no undo and no history of what the rule contained — its thresholds, switches and both allowlists are gone, and rebuilding it means knowing what it said. If a group above defines the same pattern, that rule takes over, and the branch may end up more or less protected than you expect. The branches themselves are untouched.

Deleting a branch rule never deletes a branch, and deleting a branch never deletes a rule — a rule outlives the branch it protected, so a branch that is recreated is protected again the moment it exists.

Open Settings → Branch rules, find the pattern, and choose the bin icon at the end of its row. The confirmation names the pattern and what it protected.

Terminalbash
gitmoap branch-rules delete --repo-id <id> --branch-rule-id <id>
curlbash
curl -X DELETE https://api.gitmoap.example/repositories/{repo_id}/branch-rules/{branch_rule_id} \
-H "Authorization: Bearer $GITMOAP_TOKEN"

Protect a branch across every repository in a group

A group branch rule has the same shape as a repository one and reaches every repository beneath the group, at any depth. This is the setting worth reaching for first: one main rule on the top-level group is the whole organisation's default, and repositories that genuinely need something different can override that one pattern locally.

Open the group, choose Settings, then Branch rules, then New rule. The form is the one on a repository. The rule is in force for every repository in the group and its subgroups from the moment you save it.

Terminalbash
gitmoap branch-rules create-group \
--group-id <id> \
--branch-pattern <branch pattern>
curlbash
curl -X POST https://api.gitmoap.example/groups/{group_id}/branch-rules \
-H "Authorization: Bearer $GITMOAP_TOKEN" \
-H "Content-Type: application/json" \
-d '{ "branch_pattern": "<string>" }'

A repository and its parent group may both define main, and that is the inheritance model rather than a clash — the nearer rule simply wins. Two rules for the same pattern in the same scope is what is refused.

Enforce a rule for every repository in a group

Send "enforcement": "enforced" when you create or change a group's branch rule, or with a group's push rules. From then on every repository and subgroup beneath keeps the rule at least as strict as the group's. Send "enforcement": "default" to let the scopes beneath decide for themselves again. Either way you need Owner on the group, and so does any later change to an enforced rule, and deleting it.

json
{
"branch_pattern": "main",
"allowed_to_push": "owner",
"allowed_to_merge": "developer",
"require_code_owner_approval": true,
"enforcement": "enforced"
}

A repository beneath can still add its own rule for main — to raise the merge level, or to name a service account on an allowlist the group's rule also names — but not one that lowers a level, allows a force-push, or drops code-owner approval. A repository rule written before the group enforced main stays stored and is tightened wherever it is looser.

Review and change a group's branch rules

A group's listing shows what that group declares, never what it inherits from its own parents. To see everything in force for a particular repository, ask that repository for its inherited set.

Open the group, choose Settings, then Branch rules. Each row shows the pattern, the thresholds and the switches, exactly as on a repository.

Terminalbash
gitmoap branch-rules list-group --group-id <id>
curlbash
curl -X GET https://api.gitmoap.example/groups/{group_id}/branch-rules \
-H "Authorization: Bearer $GITMOAP_TOKEN"

Choose a pattern to open its detail panel, including both allowlists.

Terminalbash
gitmoap branch-rules get-group --group-id <id> --branch-rule-id <id>
curlbash
curl -X GET https://api.gitmoap.example/groups/{group_id}/branch-rules/{branch_rule_id} \
-H "Authorization: Bearer $GITMOAP_TOKEN"

Edit the rule in place and Save. The change reaches every repository beneath the group at once, including any that were relying on the old thresholds.

Terminalbash
gitmoap branch-rules update-group --group-id <id> --branch-rule-id <id>
curlbash
curl -X PATCH https://api.gitmoap.example/groups/{group_id}/branch-rules/{branch_rule_id} \
-H "Authorization: Bearer $GITMOAP_TOKEN"

Remove a group branch rule

Warning

Deleting a group branch rule unprotects that pattern across every repository in the group and its subgroups in one action, and the rule's contents are not recoverable. A repository — or a subgroup between it and here — that defines the same pattern itself keeps its own rule, so the effect is uneven by design: some repositories lose protection and others do not. Check the inherited listing on a repository or two before you delete.

Open the group's Settings → Branch rules, find the pattern, and choose the bin icon. The confirmation names the pattern and the group it is being removed from.

Terminalbash
gitmoap branch-rules delete-group --group-id <id> --branch-rule-id <id>
curlbash
curl -X DELETE https://api.gitmoap.example/groups/{group_id}/branch-rules/{branch_rule_id} \
-H "Authorization: Bearer $GITMOAP_TOKEN"

Constrain what may be pushed

Push rules are one set of settings per repository, so there is nothing to create — you change the set, and it is created the first time you do. Send only the fields you want to change; the rest keep the values they had.

Open the repository, choose Settings, then Push rules. Each setting is a switch, a number or a pattern box, and shows the value it inherits until you set it yourself. Save applies to the next push.

Terminalbash
gitmoap push-rules update-repo --repo-id <id>
curlbash
curl -X PATCH https://api.gitmoap.example/repositories/{repo_id}/push-rules \
-H "Authorization: Bearer $GITMOAP_TOKEN"

What each setting refuses:

SettingA push is refused when
reject_unsigned_commitsa new commit carries no signature
commit_message_regexa new commit's message does not match the pattern
author_email_regexa new commit's author email does not match the pattern
require_issue_referencea new commit's message names no work item — #412, an epic's &17, or another repository's acme/api#412
require_closing_issue_referencea new commit's message does not say it closes a work item, as in Fixes #412 — naming one without a closing word is not enough
prohibited_file_names_regexa changed file's path matches the pattern
max_file_size_mba changed file is larger than the limit; 0 means no limit

Each of the three pattern fields is an ordinary regular expression, between 1 and 255 characters, and it has to compile — an invalid one is refused when you save it rather than silently disabling the rule at push time. Send null to clear a pattern and go back to inheriting it.

Those seven are the whole set, and the request body takes nothing else: a field it does not list is refused, so a setting you send is never quietly ignored. The effective rules also name override_group_rules, reject_non_dco_commits, prevent_secret_files, check_author_is_user, reject_unverified_users, prevent_tag_removal, commit_message_reject_regex and branch_name_regex; none of them can be set yet, each always reads as off, and they change no push outcome. Where you want their effect, build it out of the seven: prohibited_file_names_regex keeps a path out of a push, and author_email_regex constrains who may author a commit.

require_issue_reference and require_closing_issue_reference are one check, not two — asking for both gives you the stricter one, and no commit is examined twice. Two things are exempt: merge commits, whose message git writes rather than a person, and tag pushes, because a tag is not where anybody writes a commit message. The commit and file checks still run against any new commits a tag brings with it.

Apply push rules to a whole group

Set the baseline once on the group. Each repository beneath it inherits every field it does not set for itself, so a group can require signed commits everywhere while one repository raises the file-size ceiling and changes nothing else.

Open the group, choose Settings, then Push rules, and set the fields you want as the default for everything beneath. Save applies to the next push in every repository in the group.

Terminalbash
gitmoap push-rules update-group --group-id <id>
curlbash
curl -X PATCH https://api.gitmoap.example/groups/{group_id}/push-rules \
-H "Authorization: Bearer $GITMOAP_TOKEN"

Reading a group's push rules gives you that group's own settings and nothing else, which is what you want when you are trying to work out where a value in a repository came from.

Open the group's Settings → Push rules. Fields the group does not set are shown as unset rather than as their default, so you can tell the two apart.

Terminalbash
gitmoap push-rules get-group --group-id <id>
curlbash
curl -X GET https://api.gitmoap.example/groups/{group_id}/push-rules \
-H "Authorization: Bearer $GITMOAP_TOKEN"

Clear push rules and fall back to inheritance

Warning

Clearing push rules deletes the whole set for that scope in one action — every switch, every number and every pattern — and there is no undo. On a group it also removes the baseline from every repository beneath that was relying on it, which can leave a repository accepting pushes it refused a minute earlier. Note down what the set contained before you clear it: nothing else records it.

Clearing is how you go back to inheriting. It is not a way to turn rules off — a repository that clears its own set immediately picks up whatever its groups say. To loosen a rule a group sets, override the field instead: send the switch as false, max_file_size_mb as 0, or your own pattern in place of the inherited one. Clearing is idempotent: clearing a set that was never created succeeds and changes nothing.

Open the repository's Settings → Push rules and choose Reset to inherited. Note down the settings the repository declares before you confirm — nothing else records them.

Terminalbash
gitmoap push-rules delete-repo --repo-id <id>
curlbash
curl -X DELETE https://api.gitmoap.example/repositories/{repo_id}/push-rules \
-H "Authorization: Bearer $GITMOAP_TOKEN"

Open the group's Settings → Push rules and choose Clear group push rules. Every repository beneath the group loses these values on its next push, so check a repository's resolved push rules first if you are not sure what depends on them.

Terminalbash
gitmoap push-rules delete-group --group-id <id>
curlbash
curl -X DELETE https://api.gitmoap.example/groups/{group_id}/push-rules \
-H "Authorization: Bearer $GITMOAP_TOKEN"

Report a check result on a commit

An external system — a build server, a scanner, a deployment tool — reports its verdict against a commit and a context. The context is the check's name, and it is what makes the report replaceable: post ci/jenkins as pending when the build starts and again as success when it finishes, and the second post overwrites the first rather than adding a row. Contexts are yours to choose; a path-like name such as ci/jenkins or security/scan keeps a commit's list readable when several systems report on it.

Give target_url whenever you have somewhere to send a person — it is what turns a red mark into a link to the failing log.

Statuses are reported by machines, not typed by people, so there is no form for this in the web app. What you see is the result: the checks appear on the commit and on any merge request whose head is that commit, each with its state, its description and a link to its target_url.

Terminalbash
gitmoap commit-statuses create \
--repo-id <id> \
--commit-ref <id> \
--context <context> \
--state pending|running|success|failure|error
curlbash
curl -X POST https://api.gitmoap.example/repositories/{repo_id}/commits/{commit_ref}/statuses \
-H "Authorization: Bearer $GITMOAP_TOKEN" \
-H "Content-Type: application/json" \
-d '{ "context": "<string>", "state": "<one of error | failure | pending | running | success>" }'

The commit is named by its full 40-character hash — a short hash is refused. Gitmoap does not check that the commit exists: a status posted against a hash that never arrives is stored, and surfaces only if something asks for that exact hash. That is what keeps a build agent reporting slightly ahead of a push from failing.

Read the checks on a commit

The listing returns every status on the commit, one per context, newest values in place — plus a combined state, which is the single answer for a commit that several systems report on. It is the worst state present, folded into three values: any failure or error makes it failure, otherwise anything pending or running makes it pending, and only when every context is success is the combined state success. A combined state is never error or running — read the individual contexts to tell an errored check from a failed one. A commit nobody has reported on comes back with an empty list and no combined state at all, which is different from a green one.

Open a commit, or a merge request whose head is that commit. The checks panel lists each context with its state and description; choose one to follow its link to the reporting system. The combined state is the badge at the top of the panel.

Terminalbash
gitmoap commit-statuses list --repo-id <id> --commit-ref <id>
curlbash
curl -X GET https://api.gitmoap.example/repositories/{repo_id}/commits/{commit_ref}/statuses \
-H "Authorization: Bearer $GITMOAP_TOKEN"

A commit status is a report and not a gate. Nothing refuses a merge because a context is red. The merge checks are a separate list: draft state, the branch rule's merge threshold, code-owner approval, the approval rules, a rejected review, unresolved discussion threads, an unmerged merge request below this one in a stack and — where the repository requires them — a linked work item, its Proof of Done criteria, and every pipeline that applies to the merge request's head having passed (Merge request settings).

Reference

Branch rules — repository

5 endpoints

GET/repositories/{repo_id}/branch-rulesbranch_rule:read

List branch rules for a repository

list_branch_rulesGET /repositories/{repo_id}/branch-rulesOpen in the API reference

Token scope

branch_rule:read

Send

Path
  • repo_iduuidRepository ID
Query
  • inheritedbooleanoptionalWhen true, return the rules in effect for the repository — its own plus those inherited from ancestor groups — rather than only the rules it declares itself

Refuses

  • 400Bad request — a path parameter is not a well-formed value of its type
  • 401Unauthenticated — no credential, or an invalid one
  • 403Forbidden — the caller's access level or token scope is insufficient
  • 404Repository not found

Receive

  • items[]object7 fields
    • allow_force_pushboolean
    • allowed_to_mergedeveloperguestmaintainerownerreporter
    • allowed_to_pushdeveloperguestmaintainerownerreporter
    • branch_patternstring
    • branch_rule_idstring
    • repository_idstring
    • require_code_owner_approvalboolean
  • next_tokenobjectoptional
GET/repositories/{repo_id}/branch-rules/{branch_rule_id}branch_rule:read

Get a branch rule by ID

get_branch_ruleGET /repositories/{repo_id}/branch-rules/{branch_rule_id}Open in the API reference

Token scope

branch_rule:read

Send

Path
  • repo_iduuidRepository ID
  • branch_rule_iduuidBranch rule ID

Refuses

  • 400Bad request — a path parameter is not a well-formed value of its type
  • 401Unauthenticated — no credential, or an invalid one
  • 403Forbidden — the caller's access level or token scope is insufficient
  • 404Not found

Receive

  • allow_force_pushboolean
  • allowed_to_mergedeveloperguestmaintainerownerreporter
  • allowed_to_pushdeveloperguestmaintainerownerreporter
  • branch_patternstring
  • branch_rule_idstring
  • repository_idstring
  • require_code_owner_approvalboolean
POST/repositories/{repo_id}/branch-rulesbranch_rule:manage

Create a branch rule for a repository

create_branch_rulePOST /repositories/{repo_id}/branch-rulesOpen in the API reference

Token scope

branch_rule:manage

Counts against

branch_rules_per_scope

Send

Path
  • repo_iduuidRepository ID
Body
  • allow_force_pushbooleanoptional
  • allowed_to_mergedeveloperguestmaintainerownerreporteroptional
  • allowed_to_pushdeveloperguestmaintainerownerreporteroptional
  • branch_patternstring
  • enforcementdefaultenforcedoptionalWhether the scopes beneath may replace a rule, or only strengthen it.
  • require_code_owner_approvalbooleanoptional

Refuses

  • 400Invalid request
  • 401Unauthenticated — no credential, or an invalid one
  • 403Forbidden — the caller's access level or token scope is insufficient
  • 404Repository not found
  • 409Branch pattern already exists
  • 422The rule would be weaker than a rule a group above enforces
  • 429Branch-rule limit reached for this repository

Receive

The same shape get_branch_rule returns — 7 fields. Go to it

Show it here
  • allow_force_pushboolean
  • allowed_to_mergedeveloperguestmaintainerownerreporter
  • allowed_to_pushdeveloperguestmaintainerownerreporter
  • branch_patternstring
  • branch_rule_idstring
  • repository_idstring
  • require_code_owner_approvalboolean
PATCH/repositories/{repo_id}/branch-rules/{branch_rule_id}branch_rule:manage

Update a branch rule

update_branch_rulePATCH /repositories/{repo_id}/branch-rules/{branch_rule_id}Open in the API reference

Token scope

branch_rule:manage

Send

Path
  • repo_iduuidRepository ID
  • branch_rule_iduuidBranch rule ID
Body
  • allow_force_pushobjectoptional
  • allowed_to_mergeobjectoptional
  • allowed_to_pushobjectoptional
  • enforcementobjectoptional
  • require_code_owner_approvalobjectoptional

Refuses

  • 400Invalid request
  • 401Unauthenticated — no credential, or an invalid one
  • 403Forbidden — the caller's access level or token scope is insufficient
  • 404Not found
  • 409The request conflicts with the resource's current state; `reason` says how
  • 422The rule would be weaker than a rule a group above enforces

Receive

The same shape get_branch_rule returns — 7 fields. Go to it

Show it here
  • allow_force_pushboolean
  • allowed_to_mergedeveloperguestmaintainerownerreporter
  • allowed_to_pushdeveloperguestmaintainerownerreporter
  • branch_patternstring
  • branch_rule_idstring
  • repository_idstring
  • require_code_owner_approvalboolean
DELETE/repositories/{repo_id}/branch-rules/{branch_rule_id}branch_rule:manage

Delete a branch rule

delete_branch_ruleDELETE /repositories/{repo_id}/branch-rules/{branch_rule_id}Open in the API reference

Token scope

branch_rule:manage

Send

Path
  • repo_iduuidRepository ID
  • branch_rule_iduuidBranch rule ID

Refuses

  • 400Bad request — a path parameter is not a well-formed value of its type
  • 401Unauthenticated — no credential, or an invalid one
  • 403Forbidden — the caller's access level or token scope is insufficient
  • 404Not found
  • 409The request conflicts with the resource's current state; `reason` says how

Receive

No body.

Branch rules — group

5 endpoints

GET/groups/{group_id}/branch-rulesbranch_rule:read

List branch rules for a group

list_group_branch_rulesGET /groups/{group_id}/branch-rulesOpen in the API reference

Token scope

branch_rule:read

Send

Path
  • group_iduuidGroup ID

Refuses

  • 400Bad request — a path parameter is not a well-formed value of its type
  • 401Unauthenticated — no credential, or an invalid one
  • 403Forbidden — the caller's access level or token scope is insufficient
  • 404Group not found

Receive

  • items[]object8 fields
    • allow_force_pushboolean
    • allowed_to_mergedeveloperguestmaintainerownerreporter
    • allowed_to_pushdeveloperguestmaintainerownerreporter
    • branch_patternstring
    • branch_rule_idstring
    • enforcementdefaultenforcedWhether the scopes beneath may replace a rule, or only strengthen it.
    • group_idstring
    • require_code_owner_approvalboolean
  • next_tokenobjectoptional
GET/groups/{group_id}/branch-rules/{branch_rule_id}branch_rule:read

Get a group branch rule by ID

get_group_branch_ruleGET /groups/{group_id}/branch-rules/{branch_rule_id}Open in the API reference

Token scope

branch_rule:read

Send

Path
  • group_iduuidGroup ID
  • branch_rule_iduuidBranch rule ID

Refuses

  • 400Bad request — a path parameter is not a well-formed value of its type
  • 401Unauthenticated — no credential, or an invalid one
  • 403Forbidden — the caller's access level or token scope is insufficient
  • 404Not found

Receive

  • allow_force_pushboolean
  • allowed_to_mergedeveloperguestmaintainerownerreporter
  • allowed_to_pushdeveloperguestmaintainerownerreporter
  • branch_patternstring
  • branch_rule_idstring
  • enforcementdefaultenforcedWhether the scopes beneath may replace a rule, or only strengthen it.
  • group_idstring
  • require_code_owner_approvalboolean
POST/groups/{group_id}/branch-rulesbranch_rule:manage

Create a branch rule for a group

create_group_branch_rulePOST /groups/{group_id}/branch-rulesOpen in the API reference

Token scope

branch_rule:manage

Counts against

branch_rules_per_scope

Send

Path
  • group_iduuidGroup ID
Body
  • allow_force_pushbooleanoptional
  • allowed_to_mergedeveloperguestmaintainerownerreporteroptional
  • allowed_to_pushdeveloperguestmaintainerownerreporteroptional
  • branch_patternstring
  • enforcementdefaultenforcedoptionalWhether the scopes beneath may replace a rule, or only strengthen it.
  • require_code_owner_approvalbooleanoptional

Refuses

  • 400Invalid request
  • 401Unauthenticated — no credential, or an invalid one
  • 403Forbidden — the caller's access level or token scope is insufficient
  • 404Group not found
  • 409Branch pattern already exists
  • 422The rule would be weaker than a rule a group above enforces
  • 429Branch-rule limit reached for this group

Receive

The same shape get_group_branch_rule returns — 8 fields. Go to it

Show it here
  • allow_force_pushboolean
  • allowed_to_mergedeveloperguestmaintainerownerreporter
  • allowed_to_pushdeveloperguestmaintainerownerreporter
  • branch_patternstring
  • branch_rule_idstring
  • enforcementdefaultenforcedWhether the scopes beneath may replace a rule, or only strengthen it.
  • group_idstring
  • require_code_owner_approvalboolean
PATCH/groups/{group_id}/branch-rules/{branch_rule_id}branch_rule:manage

Update a group branch rule

update_group_branch_rulePATCH /groups/{group_id}/branch-rules/{branch_rule_id}Open in the API reference

Token scope

branch_rule:manage

Send

Path
  • group_iduuidGroup ID
  • branch_rule_iduuidBranch rule ID
Body
  • allow_force_pushobjectoptional
  • allowed_to_mergeobjectoptional
  • allowed_to_pushobjectoptional
  • enforcementobjectoptional
  • require_code_owner_approvalobjectoptional

Refuses

  • 400Invalid request
  • 401Unauthenticated — no credential, or an invalid one
  • 403Forbidden — the caller's access level or token scope is insufficient
  • 404Not found
  • 409The request conflicts with the resource's current state; `reason` says how
  • 422The rule would be weaker than a rule a group above enforces

Receive

The same shape get_group_branch_rule returns — 8 fields. Go to it

Show it here
  • allow_force_pushboolean
  • allowed_to_mergedeveloperguestmaintainerownerreporter
  • allowed_to_pushdeveloperguestmaintainerownerreporter
  • branch_patternstring
  • branch_rule_idstring
  • enforcementdefaultenforcedWhether the scopes beneath may replace a rule, or only strengthen it.
  • group_idstring
  • require_code_owner_approvalboolean
DELETE/groups/{group_id}/branch-rules/{branch_rule_id}branch_rule:manage

Delete a group branch rule

delete_group_branch_ruleDELETE /groups/{group_id}/branch-rules/{branch_rule_id}Open in the API reference

Token scope

branch_rule:manage

Send

Path
  • group_iduuidGroup ID
  • branch_rule_iduuidBranch rule ID

Refuses

  • 400Bad request — a path parameter is not a well-formed value of its type
  • 401Unauthenticated — no credential, or an invalid one
  • 403Forbidden — the caller's access level or token scope is insufficient
  • 404Not found
  • 409The request conflicts with the resource's current state; `reason` says how

Receive

No body.

Push rules

6 endpoints

GET/repositories/{repo_id}/push-rulespush_rule:read

Get effective push rules for a repository (resolved with group inheritance)

get_repo_push_rulesGET /repositories/{repo_id}/push-rulesOpen in the API reference

Token scope

push_rule:read

Send

Path
  • repo_iduuidRepository ID

Refuses

  • 400Bad request — a path parameter is not a well-formed value of its type
  • 401Unauthenticated — no credential, or an invalid one
  • 403Forbidden — the caller's access level or token scope is insufficient
  • 404Repository not found

Receive

  • author_email_regexobjectoptional
  • commit_message_regexobjectoptional
  • max_file_size_mbint32
  • prohibited_file_names_regexobjectoptional
  • reject_unsigned_commitsboolean
  • require_closing_issue_referencebooleanStricter: the reference must carry an intent to close (Fixes #412), not merely mention. Implies require_issue_reference.
  • require_issue_referencebooleanWhether every pushed commit must reference a work item. A push with a commit that does not is rejected as a whole.
PATCH/repositories/{repo_id}/push-rulespush_rule:manage

Update push rules for a repository (upsert, partial update)

update_repo_push_rulesPATCH /repositories/{repo_id}/push-rulesOpen in the API reference

Token scope

push_rule:manage

Send

Path
  • repo_iduuidRepository ID
Body
  • author_email_regexobjectoptional
  • commit_message_regexobjectoptional
  • enforcementobjectoptional
  • max_file_size_mbint32optional
  • prohibited_file_names_regexobjectoptional
  • reject_unsigned_commitsobjectoptional
  • require_closing_issue_referenceobjectoptional
  • require_issue_referenceobjectoptional

Refuses

  • 400Invalid request
  • 401Unauthenticated — no credential, or an invalid one
  • 403Forbidden — the caller's access level or token scope is insufficient
  • 404Repository not found
  • 409The request conflicts with the resource's current state; `reason` says how
  • 422The rules would be weaker than rules a group above enforces

Receive

  • author_email_regexobjectoptional
  • commit_message_regexobjectoptional
  • enforcementobjectoptional
  • max_file_size_mbint32optional
  • prohibited_file_names_regexobjectoptional
  • reject_unsigned_commitsobjectoptional
  • require_closing_issue_referenceobjectoptional
  • require_issue_referenceobjectoptional
DELETE/repositories/{repo_id}/push-rulespush_rule:manage

Delete push rules for a repository

delete_repo_push_rulesDELETE /repositories/{repo_id}/push-rulesOpen in the API reference

Token scope

push_rule:manage

Send

Path
  • repo_iduuidRepository ID

Refuses

  • 400Bad request — a path parameter is not a well-formed value of its type
  • 401Unauthenticated — no credential, or an invalid one
  • 403Forbidden — the caller's access level or token scope is insufficient
  • 404Repository not found
  • 409The request conflicts with the resource's current state; `reason` says how

Receive

No body.

GET/groups/{group_id}/push-rulespush_rule:read

Get push rules for a group (raw values, no inheritance resolution)

get_group_push_rulesGET /groups/{group_id}/push-rulesOpen in the API reference

Token scope

push_rule:read

Send

Path
  • group_iduuidGroup ID

Refuses

  • 400Bad request — a path parameter is not a well-formed value of its type
  • 401Unauthenticated — no credential, or an invalid one
  • 403Forbidden — the caller's access level or token scope is insufficient
  • 404Group not found

Receive

The same shape update_repo_push_rules returns — 8 fields. Go to it

Show it here
  • author_email_regexobjectoptional
  • commit_message_regexobjectoptional
  • enforcementobjectoptional
  • max_file_size_mbint32optional
  • prohibited_file_names_regexobjectoptional
  • reject_unsigned_commitsobjectoptional
  • require_closing_issue_referenceobjectoptional
  • require_issue_referenceobjectoptional
PATCH/groups/{group_id}/push-rulespush_rule:manage

Update push rules for a group (upsert, partial update)

update_group_push_rulesPATCH /groups/{group_id}/push-rulesOpen in the API reference

Token scope

push_rule:manage

Send

Path
  • group_iduuidGroup ID
Body
  • author_email_regexobjectoptional
  • commit_message_regexobjectoptional
  • enforcementobjectoptional
  • max_file_size_mbint32optional
  • prohibited_file_names_regexobjectoptional
  • reject_unsigned_commitsobjectoptional
  • require_closing_issue_referenceobjectoptional
  • require_issue_referenceobjectoptional

Refuses

  • 400Invalid request
  • 401Unauthenticated — no credential, or an invalid one
  • 403Forbidden — the caller's access level or token scope is insufficient
  • 404Group not found
  • 409The request conflicts with the resource's current state; `reason` says how
  • 422The rules would be weaker than rules a group above enforces

Receive

The same shape update_repo_push_rules returns — 8 fields. Go to it

Show it here
  • author_email_regexobjectoptional
  • commit_message_regexobjectoptional
  • enforcementobjectoptional
  • max_file_size_mbint32optional
  • prohibited_file_names_regexobjectoptional
  • reject_unsigned_commitsobjectoptional
  • require_closing_issue_referenceobjectoptional
  • require_issue_referenceobjectoptional
DELETE/groups/{group_id}/push-rulespush_rule:manage

Delete push rules for a group

delete_group_push_rulesDELETE /groups/{group_id}/push-rulesOpen in the API reference

Token scope

push_rule:manage

Send

Path
  • group_iduuidGroup ID

Refuses

  • 400Bad request — a path parameter is not a well-formed value of its type
  • 401Unauthenticated — no credential, or an invalid one
  • 403Forbidden — the caller's access level or token scope is insufficient
  • 404Group not found
  • 409The request conflicts with the resource's current state; `reason` says how

Receive

No body.

Commit statuses

2 endpoints

POST/repositories/{repo_id}/commits/{commit_ref}/statusescommit_status:write

Create or update a commit status

create_commit_statusPOST /repositories/{repo_id}/commits/{commit_ref}/statusesOpen in the API reference

Token scope

commit_status:write

Counts against

commit_statuses_per_commit

Send

Path
  • repo_iduuidRepository ID
  • commit_refstringCommit SHA
Body
  • contextstring
  • descriptionobjectoptional
  • stateerrorfailurependingrunningsuccess
  • target_urlobjectoptional

Refuses

  • 400Invalid request
  • 401Unauthenticated — no credential, or an invalid one
  • 403Forbidden — the caller's access level or token scope is insufficient
  • 404Repository not found
  • 409The request conflicts with the resource's current state; `reason` says how
  • 429Commit-status limit reached for this commit

Receive

  • commit_shastring
  • contextstring
  • created_atstring
  • created_byobject4 fields
    • avatar_urlobjectoptional
    • emailstring
    • idstring
    • usernamestring
  • descriptionobjectoptional
  • repository_iduuid
  • stateerrorfailurependingrunningsuccess
  • target_urlobjectoptional
  • updated_atstring
GET/repositories/{repo_id}/commits/{commit_ref}/statusescommit_status:read

List commit statuses for a commit

list_commit_statusesGET /repositories/{repo_id}/commits/{commit_ref}/statusesOpen in the API reference

Token scope

commit_status:read

Send

Path
  • repo_iduuidRepository ID
  • commit_refstringCommit SHA

Refuses

  • 400Bad request — a path parameter is not a well-formed value of its type
  • 401Unauthenticated — no credential, or an invalid one
  • 403Forbidden — the caller's access level or token scope is insufficient
  • 404Repository not found

Receive

  • combined_stateobjectoptional
  • statuses[]object12 fields
    • commit_shastring
    • contextstring
    • created_atstring
    • created_byobject4 fields
      • avatar_urlobjectoptional
      • emailstring
      • idstring
      • usernamestring
    • descriptionobjectoptional
    • repository_iduuid
    • stateerrorfailurependingrunningsuccess
    • target_urlobjectoptional
    • updated_atstring
  • total_countinteger

Limits

Branch rules are capped per scope, counted separately for each repository and each group, and the ceiling moves with your plan:

PlanBranch rules per repository or group
Free50
Pro200
Team500
Enterprise2000

One over the ceiling is refused with 429 TOO_MANY_REQUESTS and the message branch rule quota reached (max N), naming the limit in force. Nothing is partially created — the rule does not exist, and no counter has moved. The cap is per scope rather than per repository tree, so a repository at its own ceiling still inherits everything its groups define.

Reaching the ceiling usually means the patterns are too specific. One release/* rule replaces a rule per release branch and keeps working for branches nobody has cut yet. Otherwise, delete a rule you no longer need, or move a rule that several repositories share up to the group they have in common — a group rule costs one slot in the group, not one in each repository.

Allowlists: 50 entries each, per rule, for pushing and for merging separately. Going over is refused with 400 BAD_REQUEST rather than 429, because it is the shape of your request rather than a limit on your plan. If a rule needs more than 50 named identities, the threshold is the wrong tool — lower allowed_to_push to the level those people already have.

Commit statuses: distinct commit-and-context pairs per repository, and this one does move with your plan:

PlanReports per repository
Free50
Pro100
Team250
Enterprise500

The count is repository-wide rather than per commit, and it only ever grows — statuses are not deleted, so a slot taken by an old commit stays taken. Re-reporting a context you have already used on the same commit is an update in place and takes no further slot, so a build that reports ci/jenkins as pending, then running, then success on one commit consumes one. At the cap a new report is refused with 429 TOO_MANY_REQUESTS and the message commit status quota reached (max N); the status is not written. Use a stable context name per check and let the state carry the change, and post statuses only for the checks somebody reads — a reporting system that mints a fresh context per attempt burns the budget in an afternoon.

Push rules have no count cap — a repository or group has one set, not a collection. The bound that bites is per pattern: each of the three pattern fields is at most 255 characters.

To raise the branch-rule or commit-status ceiling, change your plan, or ask for an increase on the group that owns the repository. See Billing and plans.

Troubleshooting

400 BAD_REQUESTmentioning repo_id, group_id or branch_rule_id
Cause
An id in the URL is not a well-formed id — usually a path like my-group/my-repo pasted where an id belongs
What to do
Use the id. Look it up from the listing, or call the by-path endpoint that takes a path
400 BAD_REQUESTon create, mentioning the pattern
Cause
The pattern is not a legal branch pattern: it contains a space, .., ~, ^, :, ?, [ or \, starts with ., ends with .lock, or is over 255 characters
What to do
Rewrite it. Letters, digits, -, _, ., / and * are all fine — * is the one wildcard character a pattern may carry that a branch name may not
400 BAD_REQUESTmentioning an access level
Cause
You asked for guest or reporter on allowed_to_push or allowed_to_merge
What to do
The floor is developer. Use developer, maintainer or owner; to let everyone push, delete the rule instead of weakening it
400 BAD_REQUESTmentioning an allowlist
Cause
The list has more than 50 entries, repeats an id, or names an id that is not a user, a service account or a team of your organisation
What to do
Deduplicate, trim to 50, and check each id resolves. A group id or a repository id is refused — an allowlist names people, service accounts and teams, never containers
400 BAD_REQUESTon an update with no changes
Cause
The request body carried no fields, so there is nothing to apply
What to do
Send at least one field. To clear an allowlist send [], and to clear a pattern send null — both are changes, an empty body is not
400 BAD_REQUESTnaming an unknown field
Cause
A field name is misspelled, or belongs to the other kind of rule
What to do
Unknown fields are refused rather than ignored, so the message names the one that is wrong. Check it against the card above — allowed_to_push is a branch rule, author_email_regex is a push rule
400 BAD_REQUESTmentioning a regex
Cause
The pattern does not compile, is empty, or is over 255 characters
What to do
Test the expression before saving. A long alternation is usually better expressed as a shorter character class; the limit is deliberate, because these patterns run on every push. To remove a pattern rather than shorten it, send null
400 BAD_REQUESTon a commit status, mentioning the commit
Cause
The hash in the URL is not exactly 40 hex characters — usually a short hash from git log --oneline
What to do
Use the full hash: git rev-parse HEAD
400 BAD_REQUESTon a commit status, mentioning the context
Cause
The context is empty or over 255 characters
What to do
Give it a short stable name such as ci/jenkins. The context identifies the check across reports, so keep it the same between runs
401 UNAUTHORIZED
Cause
No credential was sent, the token has expired, or you asked anonymously about something that is not public
What to do
Sign in, or send a valid token. A private repository answers 401 to an anonymous caller and 404 to a signed-in non-member
403 FORBIDDENon creating or changing a rule
Cause
You can read the repository or group but you are not Maintainer on it
What to do
Ask an Owner or Maintainer to raise your level, or to make the change. Maintainer on any group above the repository is enough
403 FORBIDDENenforcing a group's rule, or changing or deleting an enforced one
Cause
Enforcement is Owner business on the group
What to do
Ask an Owner of the group to make the change
400 BAD_REQUESTasking for enforcement: enforced on a repository
Cause
Only a group's rules can be enforced
What to do
Set the rule on the group and enforce it there
422— WOULD_WEAKEN_ENFORCED_RULE naming a field
Cause
A group above enforces a rule for the same pattern, or enforces its push rules, and your change is looser in the field named
What to do
Make the change at least as strict as the enforced rule, or ask an Owner of that group to change the enforced rule itself
403 FORBIDDENon a commit status
Cause
Reporting a status needs Developer on the repository, and you are below it
What to do
Ask for Developer, or report through a service account owned by the repository or its group
403 INSUFFICIENT_SCOPE
Cause
The token is valid but was minted without the scope this call needs
What to do
Mint a new token carrying branch_rule:manage, push_rule:manage or commit_status:write. Scopes are fixed at mint time and cannot be added to a token afterwards
404 NOT_FOUNDon a repository or group you are sure exists
Cause
Either it is private and you are not a member, or the id is wrong
What to do
A private resource answers 404 rather than 403 to a non-member, so its existence stays private. Check your membership, then check the id
404 NOT_FOUNDon a rule that is visibly in the listing
Cause
The rule belongs to a different scope from the one in the URL — usually an inherited rule addressed through the repository that inherits it
What to do
Inherited rules are only editable where they are defined. Take the source from the inherited listing and call the group that owns it
409 CONFLICTon creating a branch rule
Cause
This scope already has a rule for that exact pattern
What to do
Update the existing rule instead of creating a second one. A parent group having the same pattern is not a conflict — only a duplicate within one scope is
429 TOO_MANY_REQUESTSon creating a branch rule
Cause
The scope is at its branch-rule cap for your plan — 50 on Free
What to do
Delete one you no longer need, or collapse several patterns into one glob. Nothing was created
429 TOO_MANY_REQUESTSon reporting a commit status
Cause
The repository has reached its plan's ceiling of distinct commit-and-context reports, counted across every commit and never released
What to do
Report under a stable context name rather than a new one per attempt — re-reporting the same context on the same commit is free. If every context is genuine, raise the plan or ask for an increase
Push rejected with Push rejected due to policy violations:
Cause
One or more push rules refused a commit or a file in the push
What to do
Read the numbered list: each entry names the setting that refused and the commit or file it refused. Fix the commits — amend, or rebase and rewrite — and push again. Nothing landed, so there is nothing to undo
A violation reading [prohibited_file_names_regex] File '…' matches prohibited pattern
Cause
A changed file's path matches the pattern set on the repository or on a group above it
What to do
Take the file out of the history — an amend if it is the last commit, an interactive rebase otherwise — and add it to .gitignore. Ship a redacted .env.example in its place. The match is on the path alone, so a file that is innocent but matches has to be renamed, or the pattern narrowed
A violation reading [require_issue_reference] Commit … does not reference a work item
Cause
The message names no work item, and the repository or one of its groups requires one
What to do
Amend the message to name the item — #412, an epic's &17, or acme/api#412 for another repository. require_closing_issue_reference asks for more: the message has to say it closes one, as in Fixes #412. Merge commits are exempt, so a merge that made it through is not a precedent
Push rejected with Branch is protected: requires maintainer+ to push or allowlist membership
Cause
A branch rule matched the branch and your level is below its push threshold
What to do
Open a merge request instead — the merge threshold is often lower than the push one. If this is automation, put its service account on the rule's push allowlist
A force-push is refused although you are an Owner, with a violation reading [allow_force_push] Force push to '…' is not allowed
Cause
allow_force_push is off on the rule matching that branch, and it is a property of the rule rather than of your level
What to do
Turn allow_force_push on for that rule if force-pushing is genuinely wanted there, or push a revert commit instead
A rule you created has no effect
Cause
Another pattern matches the branch more specifically, and only one rule applies
What to do
Ask the repository for its inherited branch rules and work out which pattern wins for that branch name: an exact match beats a glob, and a longer literal prefix beats a shorter one
A group's rule does not appear in the repository's branch-rule list
Cause
The plain listing returns the repository's own rules only
What to do
Ask for the inherited set. Each item then carries the scope it came from
A repository ignores a push rule set on its group
Cause
The repository sets that field itself — and false or 0 is a value, not an absence, so it overrides the group
What to do
Read the repository's push rules: they come back resolved, so the value you see is the value that applies. Send that field as null to drop the repository's own value and inherit again
A group turns a rule on and one repository stays as it was
Cause
That repository set the same field earlier, so the nearest value still wins
What to do
Send the field as null on the repository to inherit again, or clear the repository's whole push-rule set to inherit every field at once. To hold every repository to the group's value regardless, enforce the group's push rules
A merge is blocked although the branch rule lets you push
Cause
Merging is governed by allowed_to_merge, and by code-owner approval when the rule requires it — never by allowed_to_push
What to do
Open the merge request's checks panel: it names the failing check and, for code owners, who still has to approve
A merge request waits on code owners and nobody can approve it
Cause
require_code_owner_approval is on and no CODEOWNERS rule or catalog service claims the changed files
What to do
Add an owner for those paths, or turn the requirement off for that pattern. The check fails rather than passing when nothing claims a file
A merge performed by Gitmoap ignores the push rules
Cause
Push rules run on git push. A server-side merge writes the refs itself, so it is governed by branch rules and merge checks instead
What to do
Put the requirement where the merge will see it: the branch rule's merge threshold, code-owner approval, an approval rule, or a required pipeline run
A status you did not post appears on a commit
Cause
Gitmoap reports on its own work under gitmoap/specs and gitmoap/catalog
What to do
Read the description — it names what failed. These behave like any other context and are listed alongside yours
A status was accepted but never appears beside the code
Cause
It was reported against a hash that is not in the repository, usually a typo or a commit that was rewritten before it was pushed
What to do
Post it again against the hash that actually landed. The first one is not lost — ask for that exact hash and it comes back — but no commit or merge request carries it, so nobody will see it
The combined state is pending although every build passed
Cause
A context is still pending or running — often one whose reporter crashed before its final report
What to do
Look for the open context in the list and have its system report a final state, or report the terminal state yourself

Examples

Protect main for a whole organisation, and let the release automation move release branches:

json
{
"branch_pattern": "main",
"allowed_to_push": "owner",
"allowed_to_merge": "developer",
"allow_force_push": false,
"require_code_owner_approval": true
}

Nobody pushes to main by hand, any Developer can land a reviewed merge request into it, and the owners of the changed files have to approve first. Then, on the repository that cuts releases:

json
{
"branch_pattern": "release/*",
"allowed_to_push": "maintainer",
"allowed_to_merge": "maintainer",
"push_allowlist": ["8f1c0e2a-9d47-4b1e-b0a3-6c2f5d81e004"]
}

The id is the release service account. It sits at Developer and stays there; the allowlist is what lets it push to release/* without giving it Maintainer over everything else.

A conventional-commits baseline for a group, with a file-size ceiling and the paths a credential usually arrives under kept out:

json
{
"commit_message_regex": "^(feat|fix|chore|docs|refactor|test)(\\([a-z0-9-]+\\))?: .+",
"author_email_regex": "@example\\.com$",
"prohibited_file_names_regex": "(^|/)(\\.env|id_rsa|id_ed25519)$|\\.(pem|key|p12|pfx)$",
"max_file_size_mb": 25
}

Every repository beneath the group inherits all four. A repository that vendors large binaries raises its own ceiling with {"max_file_size_mb": 200} and keeps the other three exactly as they are. A repository that has to accept one of those paths sends its own narrower prohibited_file_names_regex rather than clearing the field, because clearing it only goes back to inheriting the group's.

FAQ

Does a branch rule protect a branch that does not exist yet?

Yes, and that is the usual way to use one. The rule matches on name, so release/* protects the next release branch from the moment it is created.

If I delete a branch, does its rule go with it?

No. The rule outlives the branch deliberately, so recreating the branch — by anyone — brings the protection straight back.

Can I change a rule's pattern?

No. Create the rule you want under the new pattern and delete the old one. Both are immediate, so do them in that order if you would rather not leave a gap.

Can a repository be less protected than its group?

For branch rules, yes: a repository rule for a pattern replaces the group's rule for that pattern completely, in either direction. For push rules, also yes, but only field by field — a switch sent as false, a max_file_size_mb of 0, or a looser pattern of the repository's own. Rules set the defaults for an organisation, and a repository can lower them — unless the group enforces them, in which case a repository can only make them stricter. Enforce the rules an organisation cannot do without.

Why does my repository's push-rule response show values I never set?

Because a repository's push rules come back resolved — every field carries the value that will actually apply, whether it was set here or inherited. A group's push rules come back raw, showing only what that group declares. Compare the two to see where a value comes from.

Do push rules apply to tags?

Partly. The commit and file checks run against any new commits a tag brings with it — a tag pointing at history that is already pushed brings none, so nothing is checked. The work-item reference rules skip tags entirely, because a tag introduces no commit anybody wrote.

  • Branchescreating, listing and deleting branches, and what protection changes about each
  • Merge requeststhe merge checks a branch rule turns into, and approvals and code owners
  • Approval ruleswho has to approve a merge, with the same enforced, stricter-only model
  • Groupsnesting, and why setting a rule high up reaches everything beneath it
  • Service accountsthe identity to put on an allowlist instead of a person's token
  • Access tokensminting a token with only the scopes a build agent needs
  • PipelinesGitmoap's own builds, and the merge check that requires one to pass
  • Webhookstelling an outside build system that there is something to build
  • Audit logwho changed a rule, and when