DEV Community

Cover image for codecov/codecov-action: the token it sends, OIDC, and uploads that fail green
Kay
Kay

Posted on Originally published at latchkey.dev

codecov/codecov-action: the token it sends, OIDC, and uploads that fail green

codecov/codecov-action uploads coverage reports from a GitHub Actions job to Codecov by downloading the Codecov CLI, verifying its signature and running upload-coverage. On v7, authenticate with a repository token, with use_oidc: true and id-token: write, or tokenless where Codecov allows it, and set fail_ci_if_error: true, because by default a failed upload leaves the job green.

How v7.1.1 picks a credential: OIDC unless a fork, then CODECOV_TOKEN env, then token input, else tokenless
The credential order in action.yml at v7.1.1, and what Codecov does with an upload that has none. Every failure after it is soft unless fail_ci_if_error is true.

The Codecov step is usually the last line of a test job and the one nobody looks at. That is the problem: it is designed to stay green when the upload fails, so a missing token, an expired key or an empty report shows up weeks later as a coverage badge that stopped moving.

Under the action is a Codecov CLI that it downloads and verifies with GPG, so a failure can come from the token, the signing key or the upload itself. Two errors have their own pages: failed to properly upload report and Codecov rate limit reached.

Name the report, fail on error

Run your tests with coverage output first, then hand the report to the action. Naming the file in files and setting disable_search: true stops the CLI from also picking up stray reports elsewhere in the workspace. The action needs actions/checkout to have run, and bash, curl, git and gpg on the runner; GitHub-hosted runners have all four, and a container job without them fails at the first step with Error: The following required dependencies are missing: followed by the list.

name: test
on:
  push:
    branches: [main]
  pull_request:
permissions:
  contents: read
jobs:
  test:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
      - uses: actions/setup-node@949feb2413d6458794dcd2491c4babbbce0c15c1 # v7.1.0
        with:
          node-version: 22
      - run: npm ci
      - run: npm run test:coverage   # writes coverage/lcov.info
      - uses: codecov/codecov-action@303a32d7a59b442fa8d48b6a1cc6825c09c847a5 # v7.1.1
        with:
          token: ${{ secrets.CODECOV_TOKEN }}
          files: ./coverage/lcov.info
          disable_search: true
          flags: unit
          fail_ci_if_error: true
Enter fullscreen mode Exit fullscreen mode

SHA for v7.1.1 from git ls-remote https://github.com/codecov/codecov-action refs/tags/v7.1.1^{}, read 2026-11-12.

What the action actually runs

v7 is a composite action. It checks for its dependencies, marks the workspace as a git safe directory, detects whether the run is a pull request from a fork, fetches an OIDC token if asked, picks a credential, and then runs dist/codecov.sh. That script downloads the CLI from cli.codecov.io, imports Codecov's PGP key from keybase.io/codecovsecops, checks the signature and the SHA-256 sum, prints Token length: followed by a number, and runs the CLI.

Every failure in that script goes through one function, exit_if_error, which prints the message and exits 1 only when fail_ci_if_error is true. The default is false. So Could not verify signature. Please contact Codecov if problem continues and Failed to run upload-coverage both print in red and leave the step green. The Token length: line is the fastest check you have: 0 means no credential reached the CLI.

Which token the action sends, and when it needs one

The order in action.yml is fixed. If use_oidc is true and the run is not from a fork, the OIDC token wins and any token you passed is ignored. Otherwise a CODECOV_TOKEN environment variable on the step is used, then the token input. With none of those, the upload goes out tokenless, and for a fork pull request the action sets the branch to the fork's head label, such as contributor:fix-typo, which is how Codecov recognizes a fork upload.

Whether Codecov accepts a tokenless upload is decided on its side. Its documentation, read 2026-11-12, says private repositories always need a token. A public repository needs one only for a commit on a protected branch, and not at all if the organization has switched token authentication off in the Global Upload Token settings; new organizations start with tokens Not required, existing ones with Required. Tokenless uploads share a Codecov-wide rate limit, which is what the rate limit page is about.

Repository and event What to configure
Private repository, any event Repository token or use_oidc: true
Public repository, push to a protected branch Token or OIDC, unless the organization made tokens Not required
Public repository, unprotected branch Nothing required; a token avoids the shared rate limit
Pull request from a fork Nothing: secrets and OIDC are skipped, the upload goes tokenless
Dependabot pull request Add CODECOV_TOKEN as a Dependabot secret

OIDC instead of a stored secret

With use_oidc: true the action asks GitHub for an ID token whose audience is the url input, or https://codecov.io by default, and sends that instead of a long-lived secret. The job needs id-token: write, and because setting permissions replaces the defaults, list contents: read too. If the token request fails, the id-token write permission page covers where the line has to go.

On a fork pull request the action skips the OIDC request entirely, because action.yml checks for a fork before asking, and the upload falls through to tokenless.

permissions:
  contents: read
  id-token: write
steps:
  - uses: codecov/codecov-action@303a32d7a59b442fa8d48b6a1cc6825c09c847a5 # v7.1.1
    with:
      use_oidc: true
      files: ./coverage/lcov.info
      disable_search: true
      fail_ci_if_error: true
Enter fullscreen mode Exit fullscreen mode

fail_ci_if_error, flags and the inputs that matter

v7.1.1 declares 50 inputs, most of them overrides for unusual CI setups. These are the ones that change outcomes in a normal workflow. Flags tag an upload so Codecov can report coverage per group, which in a matrix usually means one flag per OS or language version, for example flags: ${{ matrix.os }}.

Input Default Why you would set it
fail_ci_if_error false Make any failure in the script fail the step
token none Repository upload token
use_oidc false Authenticate with GitHub OIDC instead of a token
files none Comma-separated reports, added to what search finds
disable_search false Upload only what files names
flags none Comma-separated flags to group coverage
name none Upload name shown in the Codecov UI
report_type coverage test_results to upload JUnit-style test results instead
handle_no_reports_found false Do not raise when no report is found
cleanup false Download the CLI to a temporary directory and remove it (v7.1.0 and later)
verbose false Verbose CLI logging when debugging an upload

From action.yml at tag v7.1.1, read 2026-11-12. The README badge and examples still say v5.

Token, GPG and upload errors

failed to properly upload report and its siblings name the CLI stage that broke; the line to read is above it, and the upload report page walks through it. A throttled tokenless upload is on the rate limit page. Token required because branch is protected appears in the titles of issues #1580, #1653 and #1738: Codecov wants a token for that commit and none arrived, so check the Token length: line before the secret itself.

GPG failures have a specific recent cause. Codecov lost control of its keybase.io/codecovsecurity account, deleted it, and moved the key to keybase.io/codecovsecops (issue #1956). The fix is a release that reads the new account: v7.0.0 and later, or the copies published as v6.0.2 and v5.5.5. A workflow pinned to an older SHA keeps fetching from the account that no longer exists, and with fail_ci_if_error unset it says so only in the log.

# Find the pinned codecov-action refs that predate the key move
grep -rn 'codecov/codecov-action@' .github/workflows/
# Then read the token line in the last run's log
gh run view <run-id> --log | grep 'Token length'
Enter fullscreen mode Exit fullscreen mode

Which majors need workflow edits

v4 removed several inputs and v5 renamed file to files and plugin to plugins, so those two need workflow edits. v6 and v7 changed the runtime and the signing key, not the inputs.

Major Change
v4 Uploads through the Codecov CLI; tokenless unsupported except for fork pull requests; several inputs removed
v5 CLI wrapped by codecov/wrapper; file became files, plugin became plugins; new inputs such as binary, report_type, skip_validation; token opt-out for public repositories
v6 Node 24 support
v7 PGP key fetched from keybase.io/codecovsecops; v7.1.0 adds cleanup

From the README and the GitHub release notes for v6.0.0, v7.0.0, v6.0.2 and v5.5.5, read 2026-11-12.

FAQ

Is there a way to make codecov-action work with a PR from a fork?

Yes, and since v4 it works without secrets: on a fork pull request the action sends no token, sets the branch to the fork's head label, and Codecov accepts it as a tokenless fork upload. Secrets and OIDC are not available to fork runs, so do not try to pass them.

I have already configured token, but it says Token required because branch is protected. Why?

Look at the Token length: line in the step log. If it is 0, the secret did not reach the step: Dependabot runs read Dependabot secrets, fork runs get none, and a typo in the secret name yields an empty string. If it is not 0, check the token belongs to this repository or is the organization's global token.

How do I migrate from v4 to v5 and later?

Rename file to files and plugin to plugins, then pin the new SHA. From v5 to v7 no input was removed, so the rest of the step carries over; v6 needs a runner that supports Node 24, and v7 is the line that reads Codecov's new PGP key location.

References

Top comments (0)