Skip to content

ci: matrix-skip deadlock potential — add stable wrapper required check #75

@lml2468

Description

@lml2468

Discovered during the Mininglamp-OSS workflow optimization sprint (stage 5 ruleset scan).

Problem

.github/workflows/ci.yml build job currently has:

  1. Matrix: node-version: [18, 20] → emits check names build (18) / build (20)
  2. Job-level guard: if: needs.changes.outputs.code == true

The main branch protection ruleset (id 16278127) requires build (18) and build (20) as status checks.

Latent deadlock

GitHub behaviour: a matrix parent job that is skipped via job-level if: does not create any matrix children check-runs (unlike a plain job skip, which leaves a conclusion=skipped record).

If a future PR has changes.outputs.code == false, the required checks build (18) and build (20) will be permanently missing, and the PR will be unmergeable under the current ruleset.

Why it has not exploded yet

dorny/paths-filter negation patterns (code: [!**/*.md, !docs/**]) classify .github/workflows/*.yml as "not docs" → code=true even on workflow-only PRs. So merged workflow-only PRs (e.g. #74 history-check) have been triggering the matrix and passing. The moment we tighten the filter (e.g. add !.github/workflows/** to exclude pure workflow PRs from build), the deadlock fires.

This is the same root cause as octo-cli#46 (already fixed in v4 by retaining matrix + step-level skip guard). octo-admin happens to not have triggered it yet.

Reference

Proposed fix (stable wrapper)

Keep the matrix, drop the job-level if:, add a wrapper job whose name matches the ruleset:

build:
  needs: [changes]
  strategy:
    matrix:
      node-version: [18, 20]
  steps:
    - name: Skip notice
      if: needs.changes.outputs.code != true
      run: echo "docs-only PR; no work needed"
    - uses: actions/checkout@...
      if: needs.changes.outputs.code == true
    # ...rest of build steps each guarded with the same if:

build-required:
  # Stable wrapper — always runs, always reports a result, named exactly as ruleset expects
  needs: [build]
  if: always()
  runs-on: ubuntu-latest
  steps:
    - run: |
        if [ "${{ needs.build.result }}" = "failure" ] || [ "${{ needs.build.result }}" = "cancelled" ]; then
          exit 1
        fi

Then update ruleset 16278127: replace build (18) + build (20) required checks with a single build-required.

Benefit: future Node version additions do not require ruleset changes.

Scope

  • Does not affect any currently-merged PR (all 13 stage-3 merges verified clean in the scan report).
  • Does not block stage 4. Can be picked up at stage 5.

Owner

To be assigned by @cpipi_bot.

Metadata

Metadata

Assignees

No one assigned

    Labels

    priority:P2Medium — next sprinttype:choreMaintenance, deps, CI

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions