Branch rules
Nothing stops a merge of a pull request with red checks. A person with write access can also push straight to main and skip review.
A branch rule stops both. The branch takes changes only through pull requests, and a pull request merges only when its required checks passed on its head commit.
Add a rule
Admins set branch rules.
- Open the repository, then Settings, then Branch rules (
/<owner>/<repo>/settings/branches). - Under Add a rule, enter a Branch name pattern, like
mainorrelease/**. - Make sure that Require a pull request is selected.
- Select the Required checks (see the next section).
- Click Add rule.
With the src CLI (it needs the TSS_* variables from Add a secret):
src ci rule set main --require-pull --check pr/test --check pr/lint --up-to-date
Over REST:
curl -X PUT "$TSS_FORGE_URL/repos/acme/app/ci/rules/main" \
-H "Authorization: Bearer $TSS_TOKEN" \
-H "Content-Type: application/json" \
-d '{"requirePull":true,"requiredChecks":["pr/test","pr/lint"],"requireUpToDate":true}'
src ci rule setandPUTreplace the whole rule. An option that you leave out is off.- A pattern is a branch name or a glob, without
refs/heads/. It cannot start with!. - If more than one rule matches a branch, the strictest setting of each wins. A skipped check counts as passed only when every matching rule says so.
- A repository has at most 100 rules. A rule has at most 50 required checks.
Pick required checks
A required check is a job of a workflow, with the name <workflow>/<job>, like pr/test.
- Pick jobs from the plan of the base branch, in workflows that run on pull requests. A check counts only the runs of the pull request.
- A job with a matrix passes only when every cell passes.
- If the base branch has no such job, the check stays pending. If you rename or remove a job, change the rule too.
How a check counts
The forge reads the latest run of the workflow on the head commit of the pull request. Each required check is in one of three states:
- Passed: the run is over, and every cell of the job ended with success.
- Failed: a cell failed, timed out, was cancelled, or ended with an error.
- Pending: all other cases.
Three cases need care:
- A check that never started is pending, never passed. This includes a commit with no run yet, and a run that waits for an approval.
- A skipped check fails. If the rule has Count skipped checks as passed (
--skipped-passes,skippedCountsAsPassed), it passes. This setting is off by default. For example, a job is skipped when a secret that it needs has no value at the trust level of the run. - A failed plan keeps the check pending. If the plan of the head commit fails, the run of the workflow has no jobs, so its checks never pass. Fix the errors that the run shows, and push again. See When the plan fails.
Require an up-to-date branch
Select Require the branch to be up to date (--up-to-date, requireUpToDate). Then a pull request merges only when its head contains the latest commit of the base branch. If the head is behind, merge the base branch into it, or rebase it.
Protected CI pushes
Require a pull request refuses every direct push to the branch, with one exception. If the push permission of a job in a protected run names the branch, that job can push. For example: permissions: { push: ['main'] }.
- A trusted or untrusted run never pushes to a branch with a rule. See the permissions table.
- A branch with a rule counts as a protected ref. So a
manual()run on it is protected. - A merge of a pull request is not a push. The rule does not refuse it.
Hidden checks as required checks
A hidden workflow is a workflow file that only admins see. Admins keep it under Hidden workflows in the repository settings. To require one of its jobs, add the check as hidden:<workflow>/<job>.
- Only the runs of the hidden workflow count for a
hidden:check. - A
<workflow>/<job>check counts only the workflows of the repository. So a hidden workflow and a repository workflow with the same name never decide the checks of the other. - Contributors see the result, the summary, and the repro command that an admin wrote. The logs, findings and artifacts are for admins only.
One gate for every merge
Every merge goes through one gate in the forge. The merge button merges through it, and REST, MCP and src read the same result:
- REST:
GET /api/v1/repos/<owner>/<repo>/pulls/<number>/checks - MCP: the tool
get_pull_request_checks - CLI:
src ci checks <pr>
What the pull request shows
Open the pull request. Above the merge button, Required checks lists each check as Passed, Failed or Pending, with the reason. While something blocks the merge, the merge button is off, and the box says what blocks it. The CLI shows the same text:
src ci checks 7
Pull request #7 into main, head 1a2b3c4
NAME STATE REASON
pr/lint passed Passed on 1a2b3c4
pr/test pending Job test (shard: 2) is running
Up to date: no
May merge: no. This pull request can't merge into main yet. Required check pr/test is pending. Job test (shard: 2) is running. The branch is not up to date with main. Merge main into it, or rebase it.
When every required check passed and the branch is up to date, the merge button is available.