Skip to content

Fix: Clearing Stale GitHub Actions Status Checks on Main

In a high-velocity CI/CD environment, rapid pushes to the main or master branch often result in a “pile-up” of GitHub Actions runs. This leads to out-of-order deployments or, worse, stale status checks that block branch protection rules even if the latest commit is healthy. As a Cloud Architect, ensuring that the CI pipeline reflects the current state of the repository—rather than a queue of historical failures—is critical for deployment integrity.

The Problem: Ghost Checks and Queue Congestion

Section titled “The Problem: Ghost Checks and Queue Congestion”

When a developer pushes three times in five minutes, GitHub Actions defaults to running all three workflows simultaneously (depending on runner availability). If the second run fails but the third succeeds, the GitHub UI may still display a “Failure” or “Pending” icon in the commit history or Pull Request status, even though the latest code is valid.

While not a traditional crash log, the error manifests in the GitHub Actions API response or the UI Status badge:

{
"name": "continuous-integration/workflow",
"status": "completed",
"conclusion": "failure",
"started_at": "2023-10-27T10:00:00Z",
"completed_at": "2023-10-27T10:05:00Z",
"output": {
"title": "Stale Check Result",
"summary": "This result belongs to a superseded commit but is still blocking the merge."
}
}

In the GitHub UI, you will see: Some checks were not successful. 1 failing and 2 successful checks. (Even if the failing check belongs to an older commit ID in the same push sequence).


  1. Concurrency Conflict: Check if the .github/workflows/*.yml lacks a concurrency key.
  2. Branch Protection Rules: Verify if “Require status checks to be up to date before merging” is enabled in Settings > Branches.
  3. Runner Saturation: Check if self-hosted runners are bogged down by “queued” jobs from superseded commits.
  4. GitHub Environment Lock: Ensure that the environment configuration isn’t locking the deployment because an old “In Progress” run hasn’t released the lock.

1. Implement Concurrency Groups (The Architectural Standard)

Section titled “1. Implement Concurrency Groups (The Architectural Standard)”

The most efficient way to “erase” or invalidate previous results is to prevent them from completing. By using concurrency with cancel-in-progress: true, GitHub automatically kills any previous run on that branch when a new push is detected.

Update your workflow YAML:

name: Production Deployment
on:
push:
branches:
- main
# Define concurrency at the workflow level
concurrency:
group: ci-production-${{ github.ref }}
cancel-in-progress: true
jobs:
deploy:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Deploy to Cloud
run: |
echo "Deploying to production environment..."

2. Programmatic Invalidation via GitHub CLI

Section titled “2. Programmatic Invalidation via GitHub CLI”

If you have “stale” checks stuck in a pending or failure state that are blocking the UI, you can use the GitHub CLI (gh) to force-cancel runs or use a script to overwrite the check status via the REST API.

Create a cleanup script cleanup_checks.sh:

#!/bin/bash
# Re-run the latest failed check to refresh the status
REPO="org/repo-name"
BRANCH="main"
# Get the ID of the most recent failed run
RUN_ID=$(gh run list --branch $BRANCH --status failure --limit 1 --json databaseId -q '.[0].databaseId')
if [ -n "$RUN_ID" ]; then
echo "Rerunning failed workflow: $RUN_ID"
gh run rerun $RUN_ID
else
echo "No failed runs found to invalidate."
fi

3. Handling Stuck “Pending” Statuses (API Fix)

Section titled “3. Handling Stuck “Pending” Statuses (API Fix)”

Sometimes a 3rd-party integration (like a legacy Jenkins bridge or a custom check) leaves a “Pending” status that never clears. You can manually set this to neutral using a curl command against the GitHub Checks API:

Terminal window
curl -X POST \
-H "Authorization: token $GITHUB_TOKEN" \
-H "Accept: application/vnd.github.v3+json" \
https://api.github.com/repos/:owner/:repo/check-runs \
-d '{
"name": "name-of-stuck-check",
"head_sha": "your-commit-sha-here",
"status": "completed",
"conclusion": "neutral",
"output": {
"title": "Manual Invalidation",
"summary": "This check was manually cleared by the Cloud Architect."
}
}'

4. Environment-Level Protection (Cloud Context)

Section titled “4. Environment-Level Protection (Cloud Context)”

If you are using GitHub Environments (e.g., production), ensure the “Wait timer” or “Required reviewers” settings are not causing the queue.

In your yaml deployment job:

jobs:
deploy:
environment:
name: production
runs-on: ubuntu-latest
steps:
- name: Deploy
run: ./deploy.sh

Note: If a run is cancelled via concurrency, GitHub effectively “erases” its impact on the Environment lock, allowing the new push to take over immediately.