From d1a2982e4151c3b43a4682dd3a84471f59fe1d32 Mon Sep 17 00:00:00 2001 From: stxkxs <139715017+stxkxs@users.noreply.github.com> Date: Fri, 7 Aug 2026 22:34:50 -0700 Subject: [PATCH] fix: the action manifest could not load, and CI could not tell MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit An input description contained `${{ toJSON(needs) }}` as an example of what to pass. Actions evaluates expressions inside input descriptions when it loads the manifest, and `needs` is not a named value there, so the action failed to load — for every consumer, with an error pointing at this file rather than at their workflow. CI was green through all of it. The workflow ran test_gate.py and never invoked the action, so it proved gate.py correct and said nothing about action.yml. The defect surfaced in the first consumer instead. A test that exercises the script but not the artifact consumers load is not testing the thing that ships. The workflow's own gate job now uses the composite action for real, so a manifest that cannot load fails here rather than downstream. That job is also this repository's required check, so the invocation is not a fixture that could be dropped as redundant. The exhaustive failure-mode coverage stays in test_gate.py against the same gate.py the action runs; what only a live invocation can prove is that the manifest loads and the inputs are wired, which is precisely what broke. --- .github/workflows/merge-gate-action.yml | 32 +++++++++++++++++++++++++ actions/merge-gate/action.yml | 7 +++++- 2 files changed, 38 insertions(+), 1 deletion(-) diff --git a/.github/workflows/merge-gate-action.yml b/.github/workflows/merge-gate-action.yml index 450f9a3..83d4602 100644 --- a/.github/workflows/merge-gate-action.yml +++ b/.github/workflows/merge-gate-action.yml @@ -18,3 +18,35 @@ jobs: - uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0 - name: every failure mode still fails run: python3 actions/merge-gate/test_gate.py + + manifest: + name: action.yml is loadable + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0 + - name: it parses + run: python3 -c "import yaml,sys; yaml.safe_load(open('actions/merge-gate/action.yml'))" + + # This job is three things at once, deliberately. + # + # It is this repository's own required check. It is also the only place the + # composite action is *invoked* rather than merely read — the first release + # shipped an action.yml that could not load at all (a ${{ }} expression in an + # input description, which Actions evaluates at manifest load) and CI was green, + # because it ran the Python and never used the action. The defect surfaced in + # the first consumer instead. A test that exercises the script but not the + # artifact consumers load is not testing the thing that ships. + # + # The failure modes are covered exhaustively by test_gate.py against the same + # gate.py this runs. What only a real invocation can prove is that the manifest + # loads and the inputs are wired, which is exactly what broke. + merge-gate: + name: merge gate + runs-on: ubuntu-latest + needs: [test, manifest] + if: always() + steps: + - uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0 + - uses: ./actions/merge-gate + with: + needs: ${{ toJSON(needs) }} diff --git a/actions/merge-gate/action.yml b/actions/merge-gate/action.yml index b22eee5..c48d8cc 100644 --- a/actions/merge-gate/action.yml +++ b/actions/merge-gate/action.yml @@ -5,7 +5,12 @@ description: >- inputs: needs: - description: 'Always ${{ toJSON(needs) }} from the calling job.' + # No ${{ }} in any description on this file. Actions evaluates expressions + # inside input descriptions when it loads the manifest, and `needs` is not a + # named value in that context, so an example written there fails the whole + # action to load — for every consumer, with an error that points here rather + # than at their workflow. See the README for the call site. + description: 'The calling job serialised needs context. Always toJSON(needs).' required: true gate-job-id: description: >-