Safeguard
Security Guides•

Why can a skipped security check still let a pull request merge?

On GitHub, skipped and neutral count as passing for required checks. Learn when a security job skips, why that matters for forks, and how to close the gap.

Safeguard Security Team
4 min read

Because GitHub treats a skipped or neutral check as a successful status for required checks. If a security job is skipped by a conditional, it reports success, and a dependent job that is skipped after a failure may not block merging. Fork pull requests hit these conditions often, so design required checks carefully.

The rule in GitHub's documentation

GitHub's troubleshooting page lists successful check statuses as success, skipped and neutral. It also lays out how skipping behaves:

CauseResult
Workflow skipped by path filter, branch filter or commit messageCheck stays Pending and blocks merging
Job skipped by a conditionalJob reports Success
Job depends on a failed jobDependent job is skipped and may not block merging

Required checks also must pass on the latest commit SHA. Results from earlier commits do not satisfy the requirement.

How this bites with external contributors

Teams often write a condition such as "run this job if secrets are available" to avoid failing on forks. For a fork pull request, secrets are not passed, so the condition is false and the job is skipped. A skipped job reports success. The required check is green and nothing was scanned.

The same pattern appears when a scan job needs a build job that failed, or when a path filter was meant to save minutes but now skips scans for lockfile edits.

How to close the gap

  1. Do not gate a security job on the presence of a secret. Make the scan work with no secrets, so it runs for forks too.
  2. Avoid requiring a workflow that path filters can skip. GitHub's advice is to avoid requiring workflows that can be skipped.
  3. Add a final summary job that depends on the scan jobs with always() in its condition, and fail it if any needed job did not succeed. Require that summary job instead of the individual ones.
  4. Make the summary job treat a result of skipped as a failure for the security job.
  5. Test it. Open a pull request from a fork and confirm the scan actually ran, not just that the check is green.

A sketch of the summary job:

gate:
  if: always()
  needs: [dependency-scan, code-scan]
  runs-on: ubuntu-latest
  steps:
    - run: |
        test "${{ needs.dependency-scan.result }}" = "success"
        test "${{ needs.code-scan.result }}" = "success"

Names matter too

A required check is matched by name. If you rename a job, the old name may stay required and never report. Keep check names stable, and review the branch protection list when you edit workflows.

Try it yourself

Click through it at your own pace, or open it full screen.

Look for the difference between a gate that ran and decided, and a status that merely shows green.

Where Safeguard fits

Safeguard gates evaluate policy on the pull request and return an explicit pass or fail. You can try it on your own repository with the free scan.

Quick check

Does a skipped required check block the merge?

Not necessarily. A job skipped by a conditional reports Success. A workflow skipped by path or branch filters stays Pending and blocks.

What is a safe way to require several scans?

Require one summary job that runs with always(), checks each needed result, and fails unless all succeeded.

Sources

Last reviewed: 19 October 2026.

Never miss an update

Weekly insights on software supply chain security, delivered to your inbox.

Self-healing security runs on Safeguard.

Your first fix PR is minutes away.

No sales call required, even your agent can complete the purchase over MCP.