Codecov Flags: Separate Coverage Dimensions Without Splitting Repos
Use Codecov Flags to track unit, integration, and per-language coverage independently in a single repo. Includes a complete codecov.yml and CI example with per-flag thresholds and PR comment output.
09 Dec 2025, 04:24 UTC

The Problem: One Coverage Number Hides What Matters
Your CI passes with 82% coverage. Great — but the new authentication module sits at 45% while the legacy reporting code drags the average down. You can't enforce a higher bar for security-critical code without failing the whole build. Splitting into separate Codecov projects means losing the single-dashboard view and duplicating configuration. Codecov Flags solve this by letting you attach dimension labels — unit, integration, python, go — to each upload so the dashboard, PR comments, and status checks show independent percentages per flag.
How Flags Work in Practice
Each codecov upload accepts -F (or --flag) any number of times. An upload tagged -F unit -F python appears in both the "unit" and "python" views. The UI adds a flag selector dropdown; the PR comment renders a table with one row per flag showing coverage %, diff vs. base, and pass/fail status. Thresholds live in codecov.yml per flag, so a drop in integration tests fails only the integration check.
Worked Example: Monorepo with Backend and Frontend
Assume a repo with backend/ (Go) and frontend/ (TypeScript). Each has its own test suite. We want separate coverage gates: 80% for backend unit tests, 70% for frontend unit tests, and a combined 75% for integration tests that span both.
1. codecov.yml at repo root
coverage:
precision: 2
round: down
range: "70...100"
flags:
- name: backend-unit
paths:
- backend/
threshold: 80%
base: auto
- name: frontend-unit
paths:
- frontend/
threshold: 70%
base: auto
- name: integration
paths:
- backend/
- frontend/
threshold: 75%
base: auto
comment:
layout: "flags, diff, files"
behavior: default
require_changes: false
The paths filter assigns files to a flag automatically; you can still override with -F at upload time. base: auto compares against the default branch for each PR.
2. CI jobs (GitHub Actions shown)
jobs:
backend-unit:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-go@v5
with: { go-version: '1.22' }
- run: go test -coverprofile=coverage.out ./backend/...
- uses: codecov/codecov-action@v4
with:
flags: backend-unit
files: ./backend/coverage.out
token: ${{ secrets.CODECOV_TOKEN }}
frontend-unit:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with: { node-version: '20' }
- run: npm ci --prefix frontend && npm test --prefix frontend -- --coverage
- uses: codecov/codecov-action@v4
with:
flags: frontend-unit
files: ./frontend/coverage/lcov.info
token: ${{ secrets.CODECOV_TOKEN }}
integration:
runs-on: ubuntu-latest
needs: [backend-unit, frontend-unit]
steps:
- uses: actions/checkout@v4
- run: docker compose -f docker-compose.ci.yml up --abort-on-container-exit
- uses: codecov/codecov-action@v4
with:
flags: integration
files: ./coverage/integration.lcov
token: ${{ secrets.CODECOV_TOKEN }}
Each job uploads with a single flag. The integration job depends on the unit jobs so it runs after they pass, but all three uploads share the same commit SHA and token — Codecov merges them into one commit view with three flag rows.
3. PR comment result
When you open a PR, Codecov posts a comment like:
| Flag | Coverage | Diff | Status |
|---|---|---|---|
| backend-unit | 82.4% | +1.2% | ✅ Pass |
| frontend-unit | 68.9% | -0.5% | ❌ Fail |
| integration | 76.1% | +0.3% | ✅ Pass |
The frontend-unit row fails its 70% threshold while the others pass. The check suite shows three separate statuses, so you can require "backend-unit" and "integration" in branch protection while allowing "frontend-unit" to be advisory.
Trade-offs and Gotchas
- Case sensitivity:
-F Unitand-F unitcreate two distinct flags. Enforce a naming convention (lowercase, kebab-case) in your CI templates. - Flag count limits: Free tiers historically cap at 10 flags per repo. Check your plan before designing a per-service, per-language, per-test-type taxonomy.
- Parallel upload races: If two jobs upload for the same flag concurrently, the last write wins. Use
codecov.ymlmerge:settings or serialize uploads withneeds:in Actions. - Threshold base branch: Without
base: autoor an explicitbase:branch, thresholds only evaluate on the default branch. Feature branches can silently bypass gates. - Legacy comment parsers: The new flag table replaces the single-line coverage comment. Any bot that scraped the old format will break.
Verify It Works Before You Commit
- Create a test repository and push the
codecov.ymlabove. - Run the three CI jobs (or simulate with
codecov -F backend-unit -f coverage.outlocally using a test token). - Open a PR and confirm the comment shows three rows with independent statuses.
- Call
GET /repos/:owner/:repo/commits/:sha/flagswith your API token — the JSON should return coverage, threshold, and status per flag. - Intentionally delete a test in
frontend/, push, and watch only the frontend-unit check turn red. - Visit the Codecov dashboard, open the flag selector, and verify each flag has its own historical trend line.
If all six checks pass, you have a working multi-dimensional coverage gate that scales with your repo — no extra projects, no manual aggregation scripts.
0 replies
A thoughtful contribution can make all the difference. Be the first to share one.