Repository rules
- See which rules apply to a repository
- Read a single branch rule
- Protect a branch
- Change who can push or merge to a protected branch
- Remove a branch rule
- Protect a branch across every repository in a group
- Enforce a rule for every repository in a group
- Review and change a group's branch rules
- Remove a group branch rule
- Constrain what may be pushed
- Apply push rules to a whole group
- Clear push rules and fall back to inheritance
- Report a check result on a commit
- Read the checks on a commit
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
Protect your default branch
Choose Settings, then Branch rules, then New rule.
Enter
mainas the pattern, and pick the lowest level that may push and the lowest that may merge.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:readandbranch_rule:manage,push_rule:readandpush_rule:manage,commit_status:readandcommit_status:write. Scopes are fixed when the token is minted, so a build agent that only reports results should carrycommit_status:writeand 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
| Rule | Question it answers | Where it is checked |
|---|---|---|
| Branch rule | Who 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 rule | What may this push contain? | git push only |
| Commit status | What 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:
- an exact match — a pattern with no
*in it — beats any pattern with a*; - otherwise the pattern with the longest run of literal characters before its first
*wins, sorelease/*beats*; - 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 = offSetting 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:
| Field | Beneath an enforced rule |
|---|---|
allowed_to_push, allowed_to_merge | the higher of the two levels applies |
allow_force_push | allowed only if the enforced rule allows it too |
require_code_owner_approval | required if the enforced rule requires it |
push_allowlist, merge_allowlist | only 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 limitNothing 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.
gitmoap branch-rules list --repo-id <id>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.
gitmoap push-rules get-repo --repo-id <id>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.
gitmoap branch-rules get --repo-id <id> --branch-rule-id <id>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.
gitmoap branch-rules create --repo-id <id> --branch-pattern <branch pattern>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.
gitmoap branch-rules update --repo-id <id> --branch-rule-id <id>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.
gitmoap branch-rules delete --repo-id <id> --branch-rule-id <id>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.
gitmoap branch-rules create-group \ --group-id <id> \ --branch-pattern <branch pattern>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.
{ "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.
gitmoap branch-rules list-group --group-id <id>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.
gitmoap branch-rules get-group --group-id <id> --branch-rule-id <id>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.
gitmoap branch-rules update-group --group-id <id> --branch-rule-id <id>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.
gitmoap branch-rules delete-group --group-id <id> --branch-rule-id <id>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.
gitmoap push-rules update-repo --repo-id <id>curl -X PATCH https://api.gitmoap.example/repositories/{repo_id}/push-rules \ -H "Authorization: Bearer $GITMOAP_TOKEN"What each setting refuses:
| Setting | A push is refused when |
|---|---|
reject_unsigned_commits | a new commit carries no signature |
commit_message_regex | a new commit's message does not match the pattern |
author_email_regex | a new commit's author email does not match the pattern |
require_issue_reference | a new commit's message names no work item — #412, an epic's &17, or another repository's acme/api#412 |
require_closing_issue_reference | a 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_regex | a changed file's path matches the pattern |
max_file_size_mb | a 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.
gitmoap push-rules update-group --group-id <id>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.
gitmoap push-rules get-group --group-id <id>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.
gitmoap push-rules delete-repo --repo-id <id>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.
gitmoap push-rules delete-group --group-id <id>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.
gitmoap commit-statuses create \ --repo-id <id> \ --commit-ref <id> \ --context <context> \ --state pending|running|success|failure|errorcurl -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.
gitmoap commit-statuses list --repo-id <id> --commit-ref <id>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
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 type401Unauthenticated — no credential, or an invalid one403Forbidden — the caller's access level or token scope is insufficient404Repository 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
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 type401Unauthenticated — no credential, or an invalid one403Forbidden — the caller's access level or token scope is insufficient404Not 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
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 request401Unauthenticated — no credential, or an invalid one403Forbidden — the caller's access level or token scope is insufficient404Repository not found409Branch pattern already exists422The rule would be weaker than a rule a group above enforces429Branch-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 referenceToken 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 request401Unauthenticated — no credential, or an invalid one403Forbidden — the caller's access level or token scope is insufficient404Not found409The request conflicts with the resource's current state; `reason` says how422The 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 referenceToken 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 type401Unauthenticated — no credential, or an invalid one403Forbidden — the caller's access level or token scope is insufficient404Not found409The 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
Token scope
branch_rule:read
Send
Path
- group_iduuidGroup ID
Refuses
400Bad request — a path parameter is not a well-formed value of its type401Unauthenticated — no credential, or an invalid one403Forbidden — the caller's access level or token scope is insufficient404Group 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
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 type401Unauthenticated — no credential, or an invalid one403Forbidden — the caller's access level or token scope is insufficient404Not 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
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 request401Unauthenticated — no credential, or an invalid one403Forbidden — the caller's access level or token scope is insufficient404Group not found409Branch pattern already exists422The rule would be weaker than a rule a group above enforces429Branch-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 referenceToken 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 request401Unauthenticated — no credential, or an invalid one403Forbidden — the caller's access level or token scope is insufficient404Not found409The request conflicts with the resource's current state; `reason` says how422The 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 referenceToken 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 type401Unauthenticated — no credential, or an invalid one403Forbidden — the caller's access level or token scope is insufficient404Not found409The 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)
Token scope
push_rule:read
Send
Path
- repo_iduuidRepository ID
Refuses
400Bad request — a path parameter is not a well-formed value of its type401Unauthenticated — no credential, or an invalid one403Forbidden — the caller's access level or token scope is insufficient404Repository 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. Impliesrequire_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)
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 request401Unauthenticated — no credential, or an invalid one403Forbidden — the caller's access level or token scope is insufficient404Repository not found409The request conflicts with the resource's current state; `reason` says how422The 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
Token scope
push_rule:manage
Send
Path
- repo_iduuidRepository ID
Refuses
400Bad request — a path parameter is not a well-formed value of its type401Unauthenticated — no credential, or an invalid one403Forbidden — the caller's access level or token scope is insufficient404Repository not found409The 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)
Token scope
push_rule:read
Send
Path
- group_iduuidGroup ID
Refuses
400Bad request — a path parameter is not a well-formed value of its type401Unauthenticated — no credential, or an invalid one403Forbidden — the caller's access level or token scope is insufficient404Group 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)
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 request401Unauthenticated — no credential, or an invalid one403Forbidden — the caller's access level or token scope is insufficient404Group not found409The request conflicts with the resource's current state; `reason` says how422The 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
Token scope
push_rule:manage
Send
Path
- group_iduuidGroup ID
Refuses
400Bad request — a path parameter is not a well-formed value of its type401Unauthenticated — no credential, or an invalid one403Forbidden — the caller's access level or token scope is insufficient404Group not found409The 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 referenceToken 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 request401Unauthenticated — no credential, or an invalid one403Forbidden — the caller's access level or token scope is insufficient404Repository not found409The request conflicts with the resource's current state; `reason` says how429Commit-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 referenceToken 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 type401Unauthenticated — no credential, or an invalid one403Forbidden — the caller's access level or token scope is insufficient404Repository 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:
| Plan | Branch rules per repository or group |
|---|---|
| Free | 50 |
| Pro | 200 |
| Team | 500 |
| Enterprise | 2000 |
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:
| Plan | Reports per repository |
|---|---|
| Free | 50 |
| Pro | 100 |
| Team | 250 |
| Enterprise | 500 |
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-repopasted 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
guestorreporteronallowed_to_pushorallowed_to_merge - What to do
- The floor is
developer. Usedeveloper,maintainerorowner; 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 sendnull— 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_pushis a branch rule,author_email_regexis 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
401to an anonymous caller and404to 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:manageorcommit_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
404rather than403to 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
sourcefrom 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.examplein 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, oracme/api#412for another repository.require_closing_issue_referenceasks for more: the message has to say it closes one, as inFixes #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_pushis 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_pushon 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
falseor0is 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
nullto 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
nullon 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 byallowed_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_approvalis on and noCODEOWNERSrule 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/specsandgitmoap/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
pendingorrunning— 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:
{ "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:
{ "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:
{ "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.
Related
- 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