Code coverage is often treated as a reporting metric, but it can also become part of a repository's merge policy.
For example, a team may decide that a pull request should not be merged when its test coverage falls below a defined threshold. The challenge is that coverage is usually generated by the CI pipeline, while GitHub repository rules determine whether a pull request can be merged.
GitHub's REST API can be used to work with repository rulesets programmatically, including rules that enforce code coverage requirements. This is useful when organizations need to create, update, or manage similar policies across multiple repositories.
This article explains how to approach GitHub code coverage rulesets through the REST API, how the configuration fits into a GitHub Actions workflow, and what to check before applying the rule to production repositories.
What Is a GitHub Ruleset?
A ruleset defines conditions that must be satisfied before certain repository actions are allowed.
Rulesets can be used to control areas such as:
Pull request requirements
Branch updates
Commit metadata
Deployments
Code scanning
Code quality requirements
Code coverage
Instead of configuring every repository independently through the GitHub user interface, an organization can manage rulesets through APIs.
A simplified flow looks like this:
Developer opens Pull Request
|
v
GitHub Actions runs
|
v
Tests + Coverage
|
v
Coverage Result
|
v
GitHub Ruleset Check
|
+-----+-----+
| |
Pass Fail
| |
v v
Merge Block mergeThe important point is that a ruleset does not generate coverage by itself. Your CI workflow still needs to build the application, run tests, and produce the required coverage information.
Why Manage Coverage Rules Through the REST API?
For a single repository, configuring a ruleset manually may be enough.
For an organization with many repositories, manual configuration quickly becomes difficult to maintain.
Consider an organization with:
Repository A
Repository B
Repository C
Repository D
Repository E
...
Repository 100If every repository should follow the same coverage policy, manually creating and maintaining 100 rulesets introduces unnecessary administrative work.
An API-based approach allows you to automate the process.
For example:
Configuration
|
v
Automation Script
|
+---- Repository A
+---- Repository B
+---- Repository C
+---- Repository D
|
v
Consistent Coverage PolicyThis is particularly useful for platform engineering and GitHub administration teams.
How Code Coverage Fits Into GitHub Actions
Before creating a coverage rule, you need a workflow that produces coverage information.
For a .NET application, a basic workflow could look like this:
name: Test
on:
pull_request:
jobs:
test:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Setup .NET
uses: actions/setup-dotnet@v4
with:
dotnet-version: '10.x'
- name: Restore
run: dotnet restore
- name: Build
run: dotnet build --no-restore --configuration Release
- name: Test
run: >
dotnet test
--no-build
--configuration Release
--collect:"XPlat Code Coverage"The important part is the test command.
The workflow needs to generate a coverage result that GitHub or the associated coverage tooling can consume.
The exact command depends on the programming language, test framework, and coverage tooling used by the project.
Coverage and Merge Protection Are Different
A common misunderstanding is that enabling a coverage-related ruleset automatically calculates test coverage.
It does not.
Think of the system as two separate components:
Component | Responsibility |
|---|---|
Test framework | Executes tests |
Coverage tool | Calculates coverage |
GitHub Actions | Runs the CI process |
Coverage reporting | Publishes or exposes the result |
Ruleset | Applies the repository policy |
This separation is important when troubleshooting.
If the coverage value is missing, changing the ruleset may not solve the problem.
You first need to verify that the CI pipeline is correctly producing the coverage result.
Working With Rulesets Through the REST API
GitHub provides REST API endpoints for repository rulesets.
The API can be used to:
List rulesets
Create rulesets
Retrieve a ruleset
Update a ruleset
Delete a ruleset
A request generally identifies the repository and the ruleset being managed.
For example, a request to list repository rulesets can be made with:
GET /repos/OWNER/REPOSITORY/rulesetsA request to retrieve a specific ruleset uses its ruleset ID:
GET /repos/OWNER/REPOSITORY/rulesets/RULESET_IDThe exact endpoint and request structure should always be checked against the current GitHub REST API documentation because GitHub continues to expand its ruleset capabilities.
Authentication
API calls need appropriate authentication.
For automation, avoid putting a personal access token directly into source code.
For example, this is a bad pattern:
var token = "github_pat_example";Instead, store credentials securely and provide them to the automation process through an environment variable or GitHub Actions secret.
For example:
export GITHUB_TOKEN="your-token"Then your application can read it from the environment:
var token = Environment.GetEnvironmentVariable("GITHUB_TOKEN");
if (string.IsNullOrWhiteSpace(token))
{
throw new InvalidOperationException(
"GITHUB_TOKEN is not configured.");
}This keeps credentials outside the source code.
Creating a Ruleset Request
When creating a ruleset, the request contains information describing the ruleset.
A simplified API request might look like this:
{
"name": "Code Coverage",
"target": "branch",
"enforcement": "active",
"conditions": {
"ref_name": {
"include": [
"refs/heads/main"
]
}
},
"rules": []
}The exact rules included in the request depend on the ruleset functionality you are configuring.
The important idea is that a ruleset is represented as structured configuration rather than a collection of UI selections.
That makes it possible to store your policy definition alongside infrastructure or automation code.
Example: Managing Rulesets With C#
If you are building an internal administration tool, you can use HttpClient to call the GitHub REST API.
A simplified example:
using System.Net.Http.Headers;
using System.Text.Json;
var owner = "my-organization";
var repository = "sample-api";
var token = Environment.GetEnvironmentVariable("GITHUB_TOKEN");
if (string.IsNullOrWhiteSpace(token))
{
throw new InvalidOperationException(
"GITHUB_TOKEN is not configured.");
}
using var client = new HttpClient();
client.DefaultRequestHeaders.UserAgent.ParseAdd(
"CoverageRulesetManager");
client.DefaultRequestHeaders.Authorization =
new AuthenticationHeaderValue("Bearer", token);
client.DefaultRequestHeaders.Accept.ParseAdd(
"application/vnd.github+json");
var endpoint =
$"https://api.github.com/repos/{owner}/{repository}/rulesets";
using var response = await client.GetAsync(endpoint);
response.EnsureSuccessStatusCode();
var json = await response.Content.ReadAsStringAsync();
Console.WriteLine(json);This example retrieves the rulesets for a repository.
There are several details worth noticing.
User-Agent Header
GitHub expects API clients to identify themselves.
The example uses:
client.DefaultRequestHeaders.UserAgent.ParseAdd(
"CoverageRulesetManager");A meaningful application name makes API requests easier to identify.
Bearer Authentication
The token is passed using:
client.DefaultRequestHeaders.Authorization =
new AuthenticationHeaderValue("Bearer", token);The token itself remains outside the source code.
GitHub API Media Type
The request specifies:
client.DefaultRequestHeaders.Accept.ParseAdd(
"application/vnd.github+json");This is the recommended media type for current GitHub API requests.
Reading Existing Rulesets Before Creating New Ones
One of the most important production practices is to inspect existing configuration before creating another ruleset.
Otherwise, an automation script can accidentally create duplicate or conflicting policies.
For example:
var endpoint =
$"https://api.github.com/repos/{owner}/{repository}/rulesets";
using var response = await client.GetAsync(endpoint);
response.EnsureSuccessStatusCode();
var json = await response.Content.ReadAsStringAsync();
using var document = JsonDocument.Parse(json);
foreach (var ruleset in document.RootElement.EnumerateArray())
{
var name = ruleset.GetProperty("name").GetString();
Console.WriteLine(name);
}You can then search for an existing coverage ruleset before deciding whether to create or update one.
A safer automation process is:
List rulesets
|
v
Find coverage policy
|
+----+----+
| |
Found Not Found
| |
v v
Update CreateThis makes the operation much more predictable.
Designing a Coverage Policy
A coverage policy should be based on how the repository actually works.
For example, a team might require:
Main branch
|
+-- Pull request
|
+-- Tests must pass
|
+-- Coverage requirement must pass
|
+-- Required review
|
v
MergeAvoid choosing an arbitrary coverage threshold simply because a higher number looks better.
Coverage is useful, but a high percentage does not automatically mean that the tests provide strong protection.
A small amount of well-designed testing can sometimes be more valuable than a large number of low-value tests.
What Should the Coverage Rule Protect?
The policy should answer a clear question.
For example:
Should a pull request be prevented from merging if its coverage requirement is not satisfied?
That is different from asking:
What percentage of the repository is covered?
A good policy should also consider:
whether generated code is included
whether test projects are excluded
whether integration tests are included
whether only changed code is evaluated
whether the threshold applies to the entire project
whether existing technical debt is handled separately
These decisions should be made before automating the ruleset.
Testing the Policy Before Enforcement
Do not immediately activate a coverage rule across every repository.
Start with a test repository or a limited set of projects.
For example:
Coverage Policy
|
+-- Repository A
|
+-- Repository BRun pull requests with:
Coverage above the threshold.
Coverage below the threshold.
No coverage result.
Failed tests.
Coverage generated for the wrong project.
Observe how each case behaves.
This helps distinguish between a problem in the coverage pipeline and a problem in the ruleset configuration.
Handling Missing Coverage
One common failure scenario is:
Tests passed
Coverage report missingThis should not automatically be treated as equivalent to:
Coverage = 0%Those are different situations.
A missing report may indicate:
the coverage tool did not run
the test command failed before coverage generation
the coverage file was written to an unexpected location
the reporting step failed
the workflow configuration changed
When troubleshooting, inspect the GitHub Actions logs before changing the ruleset.
Managing Rulesets Across Multiple Repositories
For organizations with many repositories, you can create a central configuration.
For example:
{
"name": "Code Coverage",
"target": "branch",
"enforcement": "active"
}Your automation can then iterate through repositories:
Repository list
|
v
For each repository
|
+-- Read existing rulesets
|
+-- Find coverage policy
|
+-- Create or update
|
v
Next repositoryThis approach can help maintain consistency.
However, do not assume every repository should receive exactly the same policy.
A library, web application, infrastructure repository, and documentation repository may have very different testing models.
Best Practices
Keep the Policy Version Controlled
If rulesets are created from code, store the intended configuration in version control.
For example:
.github/
policies/
coverage-ruleset.jsonThis gives the team a history of policy changes.
Make Automation Idempotent
Running the script twice should not create two identical rulesets.
Use this pattern:
Check existing configuration
|
v
Create if missing
Update if presentUse Least-Privilege Authentication
The automation identity should have only the permissions needed to manage the intended repositories and rulesets.
Avoid using a highly privileged personal account when a more restricted automation identity can perform the task.
Test Before Enterprise-Wide Rollout
Start with a small number of repositories.
Once the behavior is understood, expand the rollout.
Monitor API Limits and Errors
Large organizations may manage hundreds or thousands of repositories.
Your automation should handle:
authentication failures
authorization errors
rate limiting
temporary API failures
repositories that are archived
repositories where the automation identity lacks access
Do not assume every API request will succeed.
Common Mistakes
Creating Duplicate Rulesets
Always check existing rulesets before creating a new one.
Putting Tokens in Source Code
Never commit API credentials into the repository.
Treating Coverage as the Only Quality Signal
Coverage does not measure every aspect of software quality.
Combine it with meaningful tests, code review, static analysis, and other repository controls.
Applying One Policy Everywhere
A centralized policy is useful, but different repositories may need different thresholds or coverage strategies.
Ignoring Existing Technical Debt
If an existing repository has low coverage, suddenly enforcing a strict requirement can prevent normal development.
Consider how the team will transition existing code.
Advantages and Disadvantages
Advantages | Disadvantages |
|---|---|
Automates ruleset management | Requires API automation knowledge |
Useful across many repositories | API permissions must be configured correctly |
Supports repeatable configuration | Policies can become complicated |
Reduces manual administration | Coverage itself does not guarantee test quality |
Can be integrated into platform tooling | Existing repositories may need migration work |
Makes policy changes easier to audit | API failures must be handled properly |
Troubleshooting Checklist
When a coverage rule does not behave as expected, check the problem in this order:
[ ] Is the GitHub Actions workflow running?
[ ] Are the tests passing?
[ ] Is coverage actually being generated?
[ ] Is the coverage result available where expected?
[ ] Is the correct repository ruleset being applied?
[ ] Is the ruleset targeting the correct branch?
[ ] Is the ruleset active?
[ ] Does the automation identity have the required permissions?
[ ] Are there duplicate or conflicting rulesets?
[ ] Is the API request returning an error?
[ ] Are API rate limits affecting automation?This sequence helps prevent spending time changing the ruleset when the real issue is in the CI workflow.
Summary
GitHub rulesets provide a way to turn repository policies into enforceable controls, while the REST API makes those policies easier to manage programmatically.
For code coverage, the important distinction is that GitHub Actions generates the coverage result, while the repository policy determines how that result participates in the development workflow.
For a small project, manually managing a ruleset may be sufficient. For organizations with many repositories, API-driven management can make policy deployment more consistent and repeatable.
The safest implementation starts by understanding the existing test and coverage workflow, defining a realistic coverage policy, checking existing rulesets, testing the configuration with a limited scope, and only then expanding the automation.
Coverage should be treated as one part of a broader engineering quality strategy rather than as a standalone measure of software quality.

Join the conversation! Your thoughts help the community grow.