---
id: cloud/guides/report-run-checks-github
title: Report Run Checks as GitHub pull request statuses
description: >-
  Post each Cypress Cloud Run Check as a GitHub commit status so failed quality
  gates block a pull request merge.
section: cloud
source_path: docs/cloud/guides/report-run-checks-github.mdx
version: 4a645eeb4b355bfca7d7bb576e96889b8b571465
updated_at: '2026-11-05T01:52:34.491Z'
---
# Report Run Checks as GitHub pull request statuses

[Run Checks](/llm/markdown/cloud/features/checks.md) evaluate recorded runs in Cypress Cloud against the thresholds you define: failed tests, flaky tests, accessibility scores, UI Coverage, and more. To turn those results into a merge gate, fetch them in CI with `getCheckResults()` and post one commit status per applicable check.

Install `@cypress/extract-cloud-results` as described in the [Results API](/llm/markdown/accessibility/connect-and-extend/results-api.md#Installation) guide, which also lists the [supported CI providers](/llm/markdown/accessibility/connect-and-extend/results-api.md#Supported-CI-Providers). 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 is separate from the Cypress [GitHub integration](/llm/markdown/cloud/integrations/github.md#Status-checks), which reports per-run-group or per-spec test pass/fail. Run Check statuses report specific thresholds that **you** configure, including Cypress Accessibility and UI Coverage assertions if relevant.

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) - define assertions and scope them to branches and tags
*   [GitHub integration](/llm/markdown/cloud/integrations/github.md#Status-checks) - built-in commit statuses for test results
*   [Flaky Test Management](/llm/markdown/cloud/features/flaky-test-management.md) - flake detection that `run.flakyTests.max` reads
