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

> codecov/codecov-action v7 explained: which token it sends, tokenless and OIDC uploads, fail_ci_if_error, flags and its errors, with a pinned workflow.

Source: https://latchkey.dev/learn/actions/codecov-codecov-action  
Updated: 2026-11-12

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.

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](/learn/github-actions/codecov-action-failed-to-properly-upload-report) and [Codecov rate limit reached](/learn/github-actions/codecov-action-token-required-rate-limit).

## 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.

```.github/workflows/test.yml
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
```

> 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](/learn/github-actions/codecov-action-token-required-rate-limit) 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](/learn/github-actions/gha-oidc-id-token-permission) 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.

```.github/workflows/test.yml
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
```

## 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](/learn/github-actions/codecov-action-failed-to-properly-upload-report) 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.

```Terminal
# 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'
```

## 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

- [codecov/codecov-action action.yml at v7.1.1 (read 2026-11-12)](https://github.com/codecov/codecov-action/blob/v7.1.1/action.yml)
- [codecov/codecov-action dist/codecov.sh at v7.1.1](https://github.com/codecov/codecov-action/blob/v7.1.1/dist/codecov.sh)
- [Codecov docs: tokens and uploading without a token](https://docs.codecov.com/docs/codecov-tokens)
- [Issue #1956: migrating keybase accounts, GPG verification failure](https://github.com/codecov/codecov-action/issues/1956)
- [codecov/codecov-action README at v7.1.1 (OIDC, migration notes)](https://github.com/codecov/codecov-action/blob/v7.1.1/README.md)

---

Latchkey runs CI/CD that repairs its own failures. Agent entry points: https://latchkey.dev/agent.txt, https://latchkey.dev/openapi.json, https://latchkey.dev/llms.txt
