Using Codecov Flags to Isolate Coverage Signals in Monorepos
Learn how Codecov Flags let you upload separate coverage reports for unit, integration, or service‑specific tests, giving each its own percentage, trend line, and GitHub Check.
20 Aug 2026, 12:55 UTC

A PR that changes only the React frontend still fails the coverage check because the backend test suite was skipped in CI. The combined coverage percentage drops, the status turns red, and the team either merges anyway or wastes time re‑running unrelated tests. The useful takeaway is to stop relying on a single coverage number for the whole repository and give each test type or service its own signal.
Codecov Flags let you upload multiple coverage reports for the same commit under distinct flag names. Each flag receives its own percentage, trend line, PR comment tab, and GitHub Check run. This separation makes branch protection practical in polyglot monorepos.
How Flags work
When the Codecov uploader runs, you pass a flag name with -F (or --flags). The uploader associates the supplied coverage file with that flag, and Codecov stores the metrics per flag per commit. Flags are declared in codecov.yml so the service knows how to treat them (e.g., carryforward, thresholds).
Declaring flags and thresholds
Flags are defined under the flags key. You can enable carryforward to reuse the last known coverage when a flag’s report is missing, preventing a false drop when a suite is intentionally skipped. You can also set per‑flag thresholds that affect the GitHub Check conclusion.
codecov:
require_git_pass: false
flags:
unit:
carryforward: true
integration:
carryforward: true
frontend:
carryforward: true
coverage:
status:
project:
default: false
flags:
unit:
target: 80%
threshold: 2%
integration:
target: 60%
threshold: 5%
frontend:
target: 75%
threshold: 3%
Flag names are case‑sensitive and must match exactly between the uploader invocation, this file, and any branch protection rule. A typo creates a new orphan flag with no history.
Uploading per flag in CI
Run the uploader once per flag after the corresponding tests produce a coverage file. Using a matrix in GitHub Actions keeps the job definition concise.
# .github/workflows/coverage.yml
name: Coverage
on: [push, pull_request]
jobs:
coverage:
strategy:
matrix:
flag: [unit, integration, frontend]
steps:
- uses: actions/checkout@v4
- name: Set up test environment
run: |
# install dependencies, compile, etc.
- name: Run tests for ${{ matrix.flag }}
run: make test-${{ matrix.flag }}
- name: Upload coverage
env:
CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }}
run: |
codecov -F ${{ matrix.flag }} \
-f coverage/${{ matrix.flag }}.xml \
-t $CODECOV_TOKEN
The command runs in the CI runner after the test step that creates coverage/${{ matrix.flag }}.xml. The token must be stored as a repository secret with upload permission. Each matrix entry generates a separate GitHub Check named codecov/project for that flag, visible in the PR checks tab.
Trade‑offs and limits
Carryforward improves signal stability but can hide a real regression if a test suite stops running silently. Pair it with a required CI job so a missing upload fails the job status, not just the coverage check.
Free tier limits (as of 2024) restrict private repositories to one active flag; additional flags require a paid plan or open‑source qualification. Verify your plan before adopting many flags.
Each flag creates a GitHub Check. The Checks API caps at 100 checks per PR. Very large flag sets can approach this ceiling.
The uploader accepts a comma‑separated --flags list, but each flag still needs its own coverage report file; you cannot split a single report into multiple flags post‑hoc.
How to verify it works
Add a codecov.yml with two flags and carryforward. Push a commit that uploads coverage for both flags using -F flag1 and -F flag2. Open the PR and confirm two separate Codecov checks appear and two tabs are visible in the Codecov UI.
On a subsequent commit, intentionally omit the upload for one flag. Verify that carryforward preserves the prior percentage in the UI and that the check conclusion stays neutral rather than failing for missing data.
Set a threshold on one flag below the current coverage, push a new commit, and confirm the check for that flag fails while the other flag remains passing. With branch protection enabled on the specific check name, the merge should be blocked only for the failing flag.
Keeping flags small, named consistently, and paired with required CI jobs gives a clear, actionable coverage signal per test type or service without forcing a single brittle total across the repository.
0 replies
A thoughtful contribution can make all the difference. Be the first to share one.