Blog · 2026-08-02

GitHub Action for a patch coverage gate

Everything in the last post runs wherever the agent runs, which makes it optional. The version that isn't optional is a GitHub job: run the tests with coverage, run tested check, and let the exit code decide the job. This is the workflow I paste into a repo:

# .github/workflows/tested.yml
name: tested
on:
  pull_request:

jobs:
  coverage:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
        with:
          fetch-depth: 0

      - uses: pnpm/action-setup@v4
      - uses: actions/setup-node@v4
        with:
          node-version: 24
          cache: pnpm

      - run: pnpm install --frozen-lockfile
      - run: pnpm test -- --coverage

      - uses: tested-hq/cli/action@main   # pin uses: to a SHA in production
        with:
          version: 0.1.9

The Action installs @tested/cli (Node 24+) and runs the same gate the CLI runs locally, against the coverage your test step just wrote. It picks up coverage/coverage-final.json (the Vitest/Jest default) automatically, and reads lcov, Cobertura, JaCoCo, gcov, and SimpleCov too, so the test step can be whatever writes coverage in your stack. fetch-depth: 0 keeps the PR base available for the diff. None of this needs an account or a token.

Require the job

A red job is still just a red job until the branch requires it. After the workflow has run once, open repo Settings → Branches, edit the protection rule (or ruleset) on your default branch, require status checks, and add the job name, coverage in the snippet above. From then on, the state the demo shows, 42.7% patch against an 80% floor, can't merge, whether the PR came out of an agent session or a Friday afternoon. The floor lives with the repo: 80% by default, failUnder in .tested.yaml to change it.

Optional: the comment and share URL

With TESTED_TOKEN set, the job also posts the scorecard as a PR comment and prints a share URL, the link you paste when someone asks why their merge is blocked:

- uses: tested-hq/cli/action@main   # pin uses: to a SHA in production
  with:
    version: 0.1.9
    push: 'true'
    pr-number: ${{ github.event.pull_request.number }}
    token: ${{ secrets.TESTED_TOKEN }}

Two behaviors worth knowing before the first run: a PR that adds no executable lines skips the patch check instead of failing it, and fork PRs run the gate fine but push: true fails there, because GitHub doesn't pass repository secrets to fork workflows. Full inputs are on the Action docs.

That closes the loop this series is about: the gap agents leave under a green badge, the check that lets an agent fix its own gap in the same session, and the job that catches whatever slips through. If the repo is on Codecov today, the swap is mostly this one workflow file. The how-to walks it end to end.