GitHub Actions conditional job skipped but required check stays pending in branch protection
A required status check must report success to merge. A job skipped by an if-condition does not report success - it reports skipped - so branch protection treats the required check as not satisfied and the PR is blocked.
What this error means
A PR is blocked from merging because a required check never reports success; the job was skipped by an if-condition.
Required check "build" is expected but the job was skipped.
Merging is blocked: required status check has not reported a successful conclusion.Diagnose it: print the context before you change anything
Most workflow-expression bugs are not syntax errors, they are an expression reading something that is empty. GitHub resolves a missing property to an empty string instead of failing the run, so a wrong reference looks like a logic bug rather than a mistake. Dump the contexts first and you will usually see the answer immediately.
- name: Dump contexts
run: |
echo '--- github ---' ; echo '${{ toJSON(github) }}'
echo '--- needs ---' ; echo '${{ toJSON(needs) }}'
echo '--- steps ---' ; echo '${{ toJSON(steps) }}'
echo '--- matrix ---' ; echo '${{ toJSON(matrix) }}'
echo '--- inputs ---' ; echo '${{ toJSON(inputs) }}'Check the context is allowed where you used it
Contexts are not available everywhere. The same expression can be valid in a step if and invalid in a job if, which is why an expression that works in one workflow fails when moved.
| Where you wrote it | Contexts available there |
|---|---|
run-name | github, inputs, vars |
concurrency | github, inputs, vars |
Top-level env | github, secrets, inputs, vars |
jobs.<id>.if | github, needs, vars, inputs |
jobs.<id>.steps.if | github, needs, strategy, matrix, job, runner, env, vars, steps, inputs |
jobs.<id>.outputs | Full access, including secrets |
Reusable workflow outputs | github, jobs, vars, inputs |
Common causes
Skipped does not equal success
Branch protection requires a successful conclusion; skipped does not count.
Required check applied to a sometimes-skipped job
Marking a path-filtered job as required blocks PRs that legitimately skip it.
How to fix it
Add a status-reporting fallback job
- Create a small always-run job that depends on the conditional one and reports success when it is skipped.
- Make that fallback job the required check instead.
gate:
needs: build
if: ${{ always() }}
runs-on: latchkey-small
steps:
- run: |
if [ "${{ needs.build.result }}" = "failure" ]; then exit 1; fiAvoid skipping required jobs
- Move the conditional logic inside the job so it always runs and exits 0 when there is nothing to do.
- Keep the required check able to report success.
Catch it before it reaches CI
Every failure in this cluster is statically detectable. actionlint parses workflow expressions, checks context availability against the same rules above, and validates needs references, so these bugs never need to cost you a run.
# one-off
docker run --rm -v "$(pwd):/repo" --workdir /repo rhysd/actionlint:latest -color
# as a job, before anything expensive runs
- uses: actions/checkout@v4
- run: |
bash <(curl -s https://raw.githubusercontent.com/rhysd/actionlint/main/scripts/download-actionlint.bash)
./actionlint -colorHow to prevent it
- Do not mark sometimes-skipped jobs as required directly.
- Use an always() gate job to translate skipped into success.