Codecov ingestion architecture: client-side normalization with queued processing
A practical architecture note on Codecov ingestion: push normalization to the CI runner, accept uploads into a queue, and process asynchronously with content-addressable storage to keep CI fast and aggregation accurate.
11 Sept 2025, 17:57 UTC

The problem with monolithic uploads
Coverage uploads spike with CI. A monolithic upload that parses, de-duplicates and stores raw LCOV on the server blocks the API and the UI during peak hours, and a single slow job can delay status checks on a pull request.
The practical takeaway is to push normalization and reduction to the CI runner and decouple acceptance from processing. Codecov's current ingestion design does this with a client-side uploader and a message queue in front of processing.
Requirements that shape the design
High-volume, bursty ingestion from distributed CI. Uploads arrive from many runners at once, often for the same commit from parallel jobs.
Low impact on CI pipelines. The upload step must not block the pipeline; status updates to GitHub, GitLab or Bitbucket should be best-effort and retryable.
Accurate aggregation across jobs. Identical coverage fragments from parallel test shards must not inflate coverage.
Data isolation for enterprise. Reports for different organizations and repositories must not be mixed, and access is enforced by the upload token and repository mapping.
Smallest suitable design
Client-side reduction before transmission. The uploader runs in the CI job and performs format normalization, compression and local de-duplication. Instead of raw coverage files, it sends normalized fragments. This reduces payload size and backend CPU.
Decoupled acceptance via a queue. Uploaded reports are accepted quickly and placed into a message queue such as Kafka. Frontend and API remain responsive while workers consume the queue.
Content-addressable storage for integrity. Each fragment is hashed. Hashes allow the backend to identify duplicate coverage across nodes in the same build and to store only unique data. The final coverage percentage reflects the unique state of the codebase.
Separate status-check path. Processing is separated from the Git provider status update. The CI job receives an acknowledgement that the report was accepted; a later worker updates the check once processing completes. This prevents transient backend latency from blocking CI.
Example configuration to bound client work. In codecov.yml you can limit processing to keep the uploader predictable in constrained containers:
# codecov.yml
coverage:
status:
project:
default:
target: auto
threshold: 1%
informational: false
parsers:
gcov:
branch_detection: true
upload:
max_concurrent_uploads: 4
Run the uploader in the CI job after tests, with a token scoped to the repository. Required permissions are read access to coverage files and network egress to codecov.io. Risk is token leakage in logs; mask the token and avoid printing it.
Trust and data boundaries
Trust boundary one is the CI environment. The uploader is untrusted input. The backend must validate repository ownership via the upload token, reject fragments for repositories the token cannot access, and enforce organization isolation.
Trust boundary two is the Git provider API. Status updates are performed with a Codecov-managed integration token. Rate limits on the provider side can delay check updates. The design treats status updates as asynchronous and idempotent.
Data boundary for content-addressable storage is the hash of the fragment plus repository and commit context. This prevents cross-tenant collision and makes duplicate detection safe within a build.
Operational checks
Verify ingestion latency. Measure the delta between the completion of the upload command in CI logs and the appearance of data in the Codecov UI for the same commit. A growing delta indicates queue backlog or worker saturation.
Test de-duplication. Upload identical coverage fragments from multiple parallel jobs for the same commit and confirm the aggregate percentage remains accurate and does not double count lines.
Audit malformed input handling. Provide a malformed LCOV file to the uploader and confirm the agent reports an error without failing the CI job. The job should continue and the error should be visible in the Codecov report.
Failure modes
Memory exhaustion in the client agent. Large coverage files from massive monorepos can exhaust runner memory if the uploader loads files fully. Mitigate with limit-based processing and streaming normalization.
Constrained containers. Highly constrained micro-containers may time out during compression and hashing. Increase resources or reduce max_concurrent_uploads.
Git provider rate limiting. API limits can delay status updates on pull requests. The system should retry with backoff and surface the delay in the UI rather than failing the upload.
Queue backlog. Sustained bursts can grow queue lag. Monitor queue depth and worker throughput, and scale workers independently of the API tier.
When the design would change
If uploads must be synchronous for compliance, the decoupled queue would need a synchronous acknowledgement path with stricter SLAs.
If clients cannot be trusted to normalize, move normalization server-side and accept larger payloads, trading backend CPU for client simplicity.
If cross-repo deduplication is required, expand the content-addressable key beyond repository and commit, with additional access controls.
Limitations to keep in mind: client-side processing assumes sufficient CI resources; API rate limits are outside Codecov control; and content-addressable deduplication is effective within a build but does not replace policy enforcement for data isolation.
0 replies
A thoughtful contribution can make all the difference. Be the first to share one.