Separate unit and integration coverage with Codecov Flags without blocking PRs
Use Codecov Flags to upload unit and integration coverage separately per commit, get independent thresholds and GitHub status checks, and make PR coverage diffs actionable without blocking on slower test layers.
09 Nov 2025, 22:08 UTC

Pull request comments that show a single coverage drop are hard to act on. A 3% overall regression could be a missing unit test for a new utility or a flaky integration suite that never covered the new endpoint. Merging on overall coverage means you either block everything or ignore the signal.
The practical fix is to stop treating coverage as one number. Codecov Flags let you upload multiple reports per commit under named labels like unit, integration, e2e. Each flag keeps its own baseline, threshold, and GitHub status context, so reviewers can see which test layer changed and branch protection can gate only the layers you care about.
Flags create independent coverage lanes per commit
A flag is a label attached to an upload. The uploader CLI accepts -F or --flag, and the GitHub Action accepts a flags input. The same commit can have several uploads, each with a different flag. Codecov stores them separately and the PR comment renders a diff table per flag when multiple flags exist.
Flag‑specific status checks appear as separate contexts, for example codecov/unit and codecov/integration. That lets you require unit coverage in branch protection while leaving integration as informational. Flag hierarchy also supports parent‑child relationships, e.g., a backend parent with unit and integration children, giving roll‑up views and granular gating at the same time.
Thresholds can be set per flag in codecov.yml. A global coverage range applies to all flags unless overridden, so explicit per‑flag thresholds avoid surprising failures.
Worked example: unit vs integration in a Node project
Assume a repo with Jest unit tests under tests/unit and integration tests under tests/integration. The goal is 90% for unit and 70% for integration, with only unit required to pass.
codecov.yml at repository root:
coverage:
range: 80..100
status:
project:
default: false
patch:
default: false
flags:
unit:
paths:
- src/*
carryforward: true
integration:
paths:
- src/*
carryforward: true
comment:
layout: \"diff, flags, files\"
behavior: default
Per‑flag thresholds are added under the coverage status section. Example structure:
coverage:
status:
project:
default: false
flags:
unit:
target: 90%
threshold: 1%
integration:
target: 70%
threshold: 2%
GitHub Actions upload step, run with write permissions for status checks:
name: test
on: [push, pull_request]
jobs:
unit:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- run: npm ci
- run: npm run test:unit -- --coverage
- uses: codecov/codecov-action@v3
with:
flags: unit
fail_ci_if_error: true
integration:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- run: npm ci
- run: npm run test:integration -- --coverage
- uses: codecov/codecov-action@v3
with:
flags: integration
fail_ci_if_error: false
Run the jobs on the same commit. Each job uploads a report with its flag label. In the Codecov dashboard Flags view you should see separate trend lines per flag. In GitHub the PR will show two status contexts. Branch protection can require codecov/unit but not codecov/integration.
To inspect flag breakdown programmatically, the Codecov API commit and PR endpoints return flag breakdowns. A request like GET /repos/:owner/:repo/commits/:sha?flags=unit,integration returns coverage per flag.
Trade‑offs and limitations to plan for
- Flag names are case‑sensitive and must match exactly between upload,
codecov.yml, and branch protection rules. A typo creates a new orphan flag with zero coverage. - Free tier limits private repositories to one flag; multiple flags require a paid plan or open‑source qualification as of 2024 pricing.
- Carryforward preserves coverage for unmodified packages within the same flag, but only within that flag. If a flag is missing on a new commit, its last coverage is not carried forward automatically.
- Merging coverage reports from different languages (e.g., Python
pytest+ Gotest) into one flag requires compatible report formats (Cobertura, lcov, etc.) or separate flags per language. - Global thresholds apply unless overridden per flag. A global 80% target will fail a flag at 75% even if that flag has no explicit threshold.
- The PR comment diff only shows flags that changed coverage relative to the base commit. Flags with identical coverage are omitted, which can hide a regression in an untouched flag.
Check results by opening the PR comment for a flag diff table, confirming two status contexts in the GitHub Checks tab, and verifying the Flags view shows independent trends after several runs.
Start with two flags, unit and integration, set a strict threshold for unit and a looser informational threshold for integration, and require only the unit status check in branch protection. That gives reviewers clear signal about where coverage moved without blocking merges on slower integration suites.
0 replies
A thoughtful contribution can make all the difference. Be the first to share one.