Introduction
Branch protection rules in GitHub repositories can require specific status checks to pass before pull requests can be merged. These status checks are typically provided by GitHub Actions workflows, but sometimes checks remain stuck in a pending state or fail unexpectedly even when the underlying workflow completes successfully. This happens when there's a mismatch between what branch protection expects and what the workflow actually reports.
Status checks are identified by name. When GitHub evaluates whether a pull request can be merged, it looks for check runs with names matching those specified in branch protection settings. If a workflow job was renamed, if the check name doesn't match exactly, or if the workflow runs in a different event context, the required check may never appear, leaving the pull request blocked indefinitely.
Understanding how GitHub Actions reports status to pull requests is essential for debugging these issues. Workflows triggered by pull_request events automatically report their status to the PR. Workflows triggered by push events to a branch associated with a PR may or may not report correctly, depending on how the branch protection is configured.
Symptoms
When deployment status checks fail or stay pending, you will observe these symptoms:
- Pull requests remain blocked with "Expected — Waiting for status to be reported" for a specific check
- The related workflow completes successfully in the Actions tab, but the PR still shows pending
- Branch protection lists check names that no longer exist after workflow refactoring
- A deployment job runs successfully on
pushbut the pull request never receives its corresponding status - Checks appear as "Required status check must pass before merging" in PR status
- After merging a workflow change, subsequent PRs get stuck waiting for old check names
Common status check states in pull requests:
``` Required status checks must pass before merging - build (expected) — Waiting for status to be reported - test (passed) — Passed - deploy (expected) — Waiting for status to be reported
Merging is blocked Required status check "build" has not been satisfied. ```
Common Causes
Several factors cause deployment status check issues:
- 1.Branch protection requires the wrong check name: The required check name in branch protection doesn't match the actual job name in the workflow. Check names are case-sensitive and must match exactly.
- 2.Workflow trigger doesn't run in PR context: Workflows triggered only on
pushevents don't automatically report status to pull requests. The workflow must run onpull_requestevents to report to the PR. - 3.Job or workflow name changed: After renaming a job, branch protection still expects the old name. The old check never appears because the job no longer exists with that name.
- 4.Matrix jobs with dynamic names: Matrix jobs may generate check names that don't match what branch protection expects (e.g.,
build (ubuntu-latest)vs justbuild). - 5.Missing workflow file on target branch: Branch protection references a workflow that doesn't exist on the branch where the PR targets.
- 6.Workflow runs in wrong branch context: The workflow runs on a feature branch but branch protection expects checks from the base branch configuration.
- 7.Skipped jobs don't satisfy requirements: If a job is skipped due to conditions, it doesn't produce a passing check, leaving requirements unsatisfied.
Step-by-Step Fix
Follow these steps to diagnose and resolve status check issues:
Step 1: Identify the expected check names
Check what branch protection requires:
```bash # Get branch protection rules gh api repos/:owner/:repo/branches/main/protection --jq '.required_status_checks.contexts'
# Or view in GitHub UI: # Settings > Branches > Branch protection rules > [Edit rule]
# List all check runs for a commit gh api repos/:owner/:repo/commits/:sha/check-runs --jq '.check_runs[].name' ```
Step 2: Compare expected checks with actual job names
Identify mismatches:
```bash # Get the workflow file content gh api repos/:owner/:repo/contents/.github/workflows/ci.yml --jq '.content' | base64 -d
# Look for job names # Jobs report as check runs with their job name
# Check actual job names in a recent run gh run view --json jobs --jq '.jobs[].name' ```
Common mismatches:
- Branch protection expects: build
- Actual job name: Build (case difference)
- Branch protection expects: deploy
- Actual job name: deploy-production (name changed)
Step 3: Fix branch protection configuration
Update branch protection to match actual job names:
```bash # Update required status checks gh api -X PUT repos/:owner/:repo/branches/main/protection/required_status_checks \ -f "strict=true" \ -f "contexts[]=\"build\"" \ -f "contexts[]=\"test\"" \ -f "contexts[]=\"deploy\""
# Or use GitHub UI to update the branch protection rule ```
Step 4: Ensure workflow runs in PR context
Make sure the workflow triggers on pull_request events:
```yaml # WRONG: Only runs on push, won't report to PR on: push: branches: [main]
# CORRECT: Runs on both push and pull_request on: push: branches: [main] pull_request: branches: [main]
# Or specifically for PR validation on: pull_request: types: [opened, synchronize, reopened] ```
Step 5: Handle matrix job check names
For matrix builds, configure check names correctly:
```yaml jobs: build: strategy: matrix: os: [ubuntu-latest, windows-latest, macos-latest] runs-on: ${{ matrix.os }} # This creates checks named "build (ubuntu-latest)", etc.
# Option 1: Branch protection includes all matrix variants # Required checks: ["build (ubuntu-latest)", "build (windows-latest)", ...]
# Option 2: Use job naming to simplify jobs: build: name: build # Override matrix-generated name strategy: matrix: os: [ubuntu-latest] runs-on: ${{ matrix.os }} # Check name will be just "build" ```
Step 6: Fix skipped job handling
Ensure required jobs always run:
```yaml # WRONG: Job might be skipped jobs: deploy: if: github.event_name == 'push' # This job won't run on pull_request, leaving check unsatisfied
# CORRECT: Always run, but handle logic inside jobs: deploy: if: always() # Run even if previous jobs failed steps: - name: Deploy (or skip for PR) if: github.event_name == 'push' run: | # Actual deployment logic
- name: Mark as passed for PR
- if: github.event_name == 'pull_request'
- run: echo "Deployment check satisfied for PR"
# Alternative: Use different jobs for PR vs push jobs: validate-pr: if: github.event_name == 'pull_request' # Satisfies "validate" check for PRs
deploy: if: github.event_name == 'push' # Satisfies "deploy" check for pushes ```
Step 7: Create check runs manually if needed
For complex scenarios, create check runs programmatically:
jobs:
create-check:
runs-on: ubuntu-latest
steps:
- name: Create check run
uses: actions/github-script@v6
with:
script: |
await github.rest.checks.create({
owner: context.repo.owner,
repo: context.repo.repo,
name: 'custom-check',
head_sha: context.sha,
status: 'completed',
conclusion: 'success'
});Verification
After fixing configuration, verify checks work correctly:
```bash # Create a test PR gh pr create --title "Test PR for status checks" --body "Testing check status"
# Watch the PR checks gh pr checks
# Check PR status gh pr view --json statusCheckRollup --jq '.statusCheckRollup[]'
# Verify all required checks pass gh pr view --json mergeable,mergeStateStatus ```
Expected output showing successful checks:
{
"mergeable": "MERGEABLE",
"mergeStateStatus": "CLEAN"
}Prevention
To prevent status check issues:
- 1.Keep check names stable: Avoid renaming jobs that are used as required status checks. If renaming is necessary, update branch protection immediately.
- 2.Use consistent workflow triggers: Ensure workflows that satisfy required checks run on
pull_requestevents, not justpush. - 3.Document required checks: Maintain documentation of which jobs satisfy which branch protection requirements.
- 4.Test branch protection after workflow changes: After modifying workflows, create a test PR to verify all checks still resolve correctly.
- 5.Use job names explicitly: Set explicit
name:properties on jobs to control check names independently of job IDs.
jobs:
build: # Job ID (internal reference)
name: Build Application # Check name (what GitHub displays)
runs-on: ubuntu-latest- 1.Monitor stuck PRs: Set up alerts or periodic checks for PRs that remain in "waiting for status" state for extended periods.
- 2.Consider using GitHub Apps for complex checks: For scenarios where you need more control over check status, consider creating a GitHub App that can create and manage check runs directly.
Related Articles
- [WordPress troubleshooting: Fix IAM Access Denied 403 - Complete Tro](fix-iam-access-denied-403)
- [GitHub Actions Artifact Expired or 403 Download Failed](github-actions-artifact-expired-403-download-failed)
- [Fix Github Actions Artifact Upload Failed File Too Large 5gb Limit Issue in GitHub Actions](github-actions-artifact-upload-failed-file-too-large-5gb-limit)
- [Fix Github Actions Aws S3 Deploy Credentials Expired Rotate Issue in GitHub Actions](github-actions-aws-s3-deploy-credentials-expired-rotate)
- [Fix Github Actions Cache Evicted Manually Workflow Fails Download Issue in GitHub Actions](github-actions-cache-evicted-manually-workflow-fails-download)
<script type="application/ld+json"> { "@context": "https://schema.org", "@type": "TechArticle", "headline": "GitHub Actions Deployment Status Check Failed or Stayed Pending Indefinitely", "description": "Resolve GitHub Actions deployment status checks stuck in pending by aligning branch protection check names, workflow triggers, and the job context that reports status back to the pull request.", "url": "https://www.fixwikihub.com/github-actions-deployment-status-check-failed-pending-indefinitely", "publisher": { "@type": "Organization", "name": "FixWikiHub", "url": "https://www.fixwikihub.com" }, "author": { "@type": "Person", "name": "FixWikiHub Editorial Team" }, "datePublished": "2026-01-23T05:57:55.652Z", "dateModified": "2026-01-23T05:57:55.652Z" } </script>