---
id: ui-coverage/guides/report-run-checks-github
title: Report Run Checks as GitHub pull request statuses in UI Coverage
description: >-
  Post UI Coverage Run Checks as GitHub commit statuses so coverage score and
  untested-element thresholds block a pull request merge.
section: ui-coverage
source_path: docs/ui-coverage/guides/report-run-checks-github.mdx
version: 3bfcda5ae98250e4e9fbda8e4a9877945b78d2b3
updated_at: '2026-11-11T20:31:20.765Z'
---
# Report Run Checks as GitHub pull request statuses

[Run Checks](/llm/markdown/cloud/features/checks.md) let you encode UI Coverage thresholds in your App Quality configuration, such as a minimum coverage score or a maximum number of untested elements, and have Cypress Cloud evaluate them on every recorded run. To surface those results on a pull request, fetch them in CI with `getCheckResults()` and post one commit status per applicable check.

This guide covers the GitHub workflow for posting Run Check statuses. For custom policy logic the built-in assertion types don't cover, such as comparing untested-element counts against a stored baseline, use the [Results API](/llm/markdown/ui-coverage/results-api.md) approach in [Block pull requests and set policies](/llm/markdown/ui-coverage/guides/block-pull-requests.md).

Install `@cypress/extract-cloud-results` as described in the [Results API](/llm/markdown/ui-coverage/results-api.md#Installation) guide. Run the status script in the same CI job that recorded the run, after `cypress run --record`. For the full `getCheckResults()` response shape and options, see [Using check results in CI](/llm/markdown/cloud/features/checks.md#Using-check-results-in-CI).

This GitHub Actions workflow records a run, then posts one [commit status](https://docs.github.com/en/rest/commits/statuses) per applicable [Run Check](/llm/markdown/cloud/features/checks.md) to the pull request. A failed check shows as a failing status on the pull request, and you can make the statuses required in your branch protection rules.

.github/workflows/cypress.yml

```
name: Cypress
on: pull_request

permissions:
  contents: read
  statuses: write

jobs:
  cypress:
    runs-on: ubuntu-latest
    env:
      CYPRESS_PROJECT_ID: abc123
      CYPRESS_RECORD_KEY: ${{ secrets.CYPRESS_RECORD_KEY }}
    steps:
      - uses: actions/checkout@v4
      - run: npm ci
      # Failing tests fail this step, but the checks still need to be reported.
      - run: npx cypress run --record
        continue-on-error: true
      - run: npm install --force https://cdn.cypress.io/extract-cloud-results/v1/extract-cloud-results.tgz
      - run: node scripts/post-check-statuses.js
        env:
          GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
```

scripts/post-check-statuses.js

```
const fs = require('fs')
const { getCheckResults } = require('@cypress/extract-cloud-results')

const STATES = { passed: 'success', failed: 'failure' }

// On pull_request events, GITHUB_SHA is a merge commit that GitHub creates.
// Statuses only show on the pull request when they're posted to its head commit.
const event = JSON.parse(fs.readFileSync(process.env.GITHUB_EVENT_PATH, 'utf8'))
const sha = event.pull_request?.head.sha ?? process.env.GITHUB_SHA

const main = async () => {
  const results = await getCheckResults()
  const checks = (results.checks ?? []).filter(
    (check) => check.status !== 'not-applicable'
  )

  for (const check of checks) {
    const missed = check.assertionResults
      .filter((assertion) => assertion.status !== 'passed')
      .map(({ type, value, status }) => `${type} ${value} (${status})`)

    const response = await fetch(
      `https://api.github.com/repos/${process.env.GITHUB_REPOSITORY}/statuses/${sha}`,
      {
        method: 'POST',
        headers: {
          authorization: `Bearer ${process.env.GITHUB_TOKEN}`,
          accept: 'application/vnd.github+json',
        },
        body: JSON.stringify({
          context: `Cypress check: ${check.name}`,
          state: STATES[check.status] ?? 'error',
          description: (missed.length
            ? `Missed: ${missed.join('; ')}`
            : 'All assertions passed'
          ).slice(0, 140),
          target_url: `${results.runUrl}/properties`,
        }),
      }
    )

    if (!response.ok) {
      throw new Error(`GitHub responded ${response.status}`)
    }
  }
}

main().catch((error) => {
  console.error(error.message)
  process.exit(1)
})
```

The script skips `not-applicable` checks, so a check scoped to `release/.*` branches only appears on pull requests from those branches. GitHub limits a status description to 140 characters, so the script truncates it and links each status to the run for details.

## See also

*   [Run Checks](/llm/markdown/cloud/features/checks.md) - UI Coverage assertion types, view matching, and run scoping
*   [Block pull requests and set policies](/llm/markdown/ui-coverage/guides/block-pull-requests.md) - custom Results API policies
*   [Results API](/llm/markdown/ui-coverage/results-api.md) - fetch coverage scores and untested elements in CI
