Skip to content

The generated All checks passed gate counts a skipped job as passed #16

Closed

Description

@AlexeyShalaev

Versions: python-library-template 8ebf45d, Copier 9.18.2

The all-checks-passed job is the one check setup_repo.py makes required, and in the generated CI it fails only on failure or cancelled (template/.github/workflows/ci.yml.jinja:66-71). A job that ends skipped counts as passed. In the generated workflow, the needed jobs are skipped only when lint fails, and the gate already fails on lint. But if someone later adds an if: or a paths filter to test-unit or test-integration, then "the tests did not run" shows up as a green required check. The template's own CI already uses the stricter rule: .github/workflows/ci.yml:77 passes only when every needed job is success.

pg-partsmith already depends on the lenient rule. Its dependency-review job runs only on pull requests (if: github.event_name == 'pull_request'), so on pushes to master it is skipped and the gate is green. That skip is by design, and it looks exactly like an accidental one.

Reproduction

# render of 8ebf45d
sed -n '59,72p' demo-lib/.github/workflows/ci.yml
# the template repository's own gate
sed -n '76,77p' .github/workflows/ci.yml
# pg-partsmith, latest push to master
id=$(gh api "repos/bedrock-python/pg-partsmith/actions/workflows/ci.yml/runs?event=push&branch=master&per_page=1" \
     --jq '.workflow_runs[0].id')
gh api "repos/bedrock-python/pg-partsmith/actions/runs/$id/jobs?per_page=100" \
  --jq '.jobs[] | select(.conclusion != "success" or .name == "All checks passed") | "\(.name)\t\(.conclusion)"'

Actual

  # Sentinel job — use this as the single required status check in branch protection.
  all-checks-passed:
    name: All checks passed
    if: always()
    needs: [lint, test-unit, test-integration]
    runs-on: ubuntu-latest
    steps:
      - name: Check all jobs succeeded
        run: |
          if [[ "${{ contains(needs.*.result, 'failure') || contains(needs.*.result, 'cancelled') }}" == "true" ]]; then
            echo "One or more required checks failed."
            exit 1
          fi
          echo "All checks passed."
      - name: Every job above succeeded
        run: echo '${{ toJSON(needs) }}' | jq -e 'all(.[]; .result == "success")'

pg-partsmith run 38169905669 (push, master, 2890538):

New dependencies carry no known vulnerability	skipped
All checks passed	success

Why

A required check is only worth something if it fails when the work did not happen. The template already settled this question for its own CI. The generated projects, where tests matter more, got the weaker version.

Proposal

Don't simply copy the template's all(... == "success"), because that would break pg-partsmith and anything else with a job skipped on purpose. Instead, require success from every job except an explicit list of jobs that may be skipped:

      - name: Check all jobs succeeded
        env:
          NEEDS: ${{ toJSON(needs) }}
          MAY_SKIP: '[]'   # jobs skipped by design, e.g. '["dependency-review"]' in pg-partsmith
        run: |
          echo "$NEEDS" | jq -e --argjson skip "$MAY_SKIP" \
            'to_entries | all(.value.result == "success" or (.value.result == "skipped" and (.key | IN($skip[]))))'

In the .jinja file, the ${{ }} needs {% raw %} as usual. I checked the jq expression with jq 1.7.1. success plus an allowed skip gives true, exit 0. A skipped test-unit that is not on the list gives false, exit 1. All success gives true, exit 0.

Activity

  1. AlexeyShalaev commented on Oct 16, 2026

    @AlexeyShalaev
    MemberAuthor

    Plan: as proposed. Every job in needs has to end success, except the jobs named in an explicit MAY_SKIP list (empty in the template), which may also end skipped. needs and the list go in through env:, and jq -e decides.

    I considered copying the template's own all(.result == "success"). As the issue says, it would turn pg-partsmith's gate red on every push to master. With the list, that skip is written down in the file as intended, and any other skip fails the gate.

    In the PR I'll run the rendered step against the cases: all success, an allowed skip, an unlisted skip, a failure, a cancellation.

  2. AlexeyShalaev commented on Oct 16, 2026

    @AlexeyShalaev
    MemberAuthor

    The fix is in #19, as proposed: success from every needed job, with skipped allowed only for jobs listed in MAY_SKIP. pg-partsmith will need MAY_SKIP: '["dependency-review"]' when it takes the change.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    enhancementNew feature or request

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions