Skip to content

Commit a85135d

Browse files
committed
fix(ci): gate rust/elixir reusables via a detect job, not inputs in job-if
rust-ci-reusable.yml and elixir-ci-reusable.yml put `hashFiles(format('{0}/<manifest>', inputs.working_directory)) != ''` directly in each job-level `if:`. That startup-failed the reusable on every push (run with 0 jobs): the `inputs` context does not exist when GitHub compiles a workflow for a non-workflow_call event, so the expression is invalid. (It was also latently broken when *called* — `hashFiles` at job level runs before any checkout, so it reads an empty workspace.) Replace with the canonical pattern: a small `detect` job checks out first, resolves `has_cargo`/`has_mix` (and the enable_audit/enable_coverage toggles for Rust) into step outputs, and every downstream job gates on `needs.detect.outputs.*` — valid in every event context and actually sees the files. No existing job names change, so consumer status-check names are unaffected. This is an estate-consumed template change (separate commit for independent review). Validated with PyYAML. No SPDX header or licence content touched. https://claude.ai/code/session_011xv3VLrqeXkpjXxUojKz82
1 parent 68d8834 commit a85135d

2 files changed

Lines changed: 72 additions & 9 deletions

File tree

.github/workflows/elixir-ci-reusable.yml

Lines changed: 28 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -98,12 +98,39 @@ permissions:
9898
contents: read
9999

100100
jobs:
101+
# Detect mix.exs AFTER checkout and gate the test job on this job's output.
102+
# Putting `hashFiles(... inputs.working_directory)` directly in the job-level
103+
# `if:` startup-failed the reusable on every push: the `inputs` context does
104+
# not exist when GitHub compiles a workflow for a non-`workflow_call` event,
105+
# and `hashFiles` at job level (pre-checkout) reads an empty workspace.
106+
# Gating on `needs` is the canonical, event-context-safe pattern.
107+
detect:
108+
timeout-minutes: 5
109+
name: Detect Mix project
110+
runs-on: ${{ inputs.runs-on }}
111+
permissions:
112+
contents: read
113+
outputs:
114+
has_mix: ${{ steps.d.outputs.has_mix }}
115+
steps:
116+
- name: Checkout repository
117+
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
118+
with:
119+
repository: ${{ github.repository }}
120+
ref: ${{ github.ref }}
121+
- id: d
122+
working-directory: ${{ inputs.working_directory }}
123+
run: |
124+
has=false; [ -f mix.exs ] && has=true
125+
echo "has_mix=$has" >> "$GITHUB_OUTPUT"
126+
101127
test:
102128
timeout-minutes: 20
103129
name: Compile + test
104130
runs-on: ${{ inputs.runs-on }}
131+
needs: detect
105132
# Guard on mix.exs so the wrapper is safe to add unconditionally.
106-
if: ${{ hashFiles(format('{0}/mix.exs', inputs.working_directory)) != '' }}
133+
if: ${{ needs.detect.outputs.has_mix == 'true' }}
107134
permissions:
108135
contents: read
109136
env:

.github/workflows/rust-ci-reusable.yml

Lines changed: 44 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -100,14 +100,48 @@ permissions:
100100
contents: read
101101

102102
jobs:
103-
# Skip the whole reusable when the repo has no Cargo.toml — lets
104-
# consumers add the wrapper unconditionally without worrying about
105-
# repos that don't ship Rust code at the moment.
103+
# Detect whether the repo ships a Cargo.toml (and resolve the opt-in
104+
# toggles) AFTER checkout, then gate every downstream job on this job's
105+
# outputs. The previous design put `hashFiles(... inputs.working_directory)`
106+
# directly in each job-level `if:`. That startup-failed the whole reusable
107+
# on every push: the `inputs` context does not exist when GitHub compiles a
108+
# workflow for a non-`workflow_call` event, and `hashFiles` at job level
109+
# (pre-checkout) reads an empty workspace anyway. Gating on `needs` instead
110+
# is the canonical pattern — valid in every event context, and it actually
111+
# sees the files because detect checks out first.
112+
detect:
113+
timeout-minutes: 5
114+
name: Detect Cargo project
115+
runs-on: ${{ inputs.runs-on }}
116+
permissions:
117+
contents: read
118+
outputs:
119+
has_cargo: ${{ steps.d.outputs.has_cargo }}
120+
run_audit: ${{ steps.d.outputs.run_audit }}
121+
run_coverage: ${{ steps.d.outputs.run_coverage }}
122+
steps:
123+
- name: Checkout repository
124+
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
125+
with:
126+
repository: ${{ github.repository }}
127+
ref: ${{ github.ref }}
128+
- id: d
129+
working-directory: ${{ inputs.working_directory }}
130+
env:
131+
ENABLE_AUDIT: ${{ inputs.enable_audit }}
132+
ENABLE_COVERAGE: ${{ inputs.enable_coverage }}
133+
run: |
134+
has=false; [ -f Cargo.toml ] && has=true
135+
echo "has_cargo=$has" >> "$GITHUB_OUTPUT"
136+
{ [ "$has" = true ] && [ "$ENABLE_AUDIT" = "true" ]; } && echo "run_audit=true" >> "$GITHUB_OUTPUT" || echo "run_audit=false" >> "$GITHUB_OUTPUT"
137+
{ [ "$has" = true ] && [ "$ENABLE_COVERAGE" = "true" ]; } && echo "run_coverage=true" >> "$GITHUB_OUTPUT" || echo "run_coverage=false" >> "$GITHUB_OUTPUT"
138+
106139
check:
107140
timeout-minutes: 20
108141
name: Cargo check + clippy + fmt
109142
runs-on: ${{ inputs.runs-on }}
110-
if: ${{ hashFiles(format('{0}/Cargo.toml', inputs.working_directory)) != '' }}
143+
needs: detect
144+
if: ${{ needs.detect.outputs.has_cargo == 'true' }}
111145
permissions:
112146
contents: read
113147
defaults:
@@ -147,8 +181,8 @@ jobs:
147181
timeout-minutes: 20
148182
name: Cargo test
149183
runs-on: ${{ inputs.runs-on }}
150-
needs: check
151-
if: ${{ hashFiles(format('{0}/Cargo.toml', inputs.working_directory)) != '' }}
184+
needs: [detect, check]
185+
if: ${{ needs.detect.outputs.has_cargo == 'true' }}
152186
permissions:
153187
contents: read
154188
defaults:
@@ -187,7 +221,8 @@ jobs:
187221
timeout-minutes: 20
188222
name: Cargo audit (security)
189223
runs-on: ${{ inputs.runs-on }}
190-
if: ${{ inputs.enable_audit && hashFiles(format('{0}/Cargo.toml', inputs.working_directory)) != '' }}
224+
needs: detect
225+
if: ${{ needs.detect.outputs.run_audit == 'true' }}
191226
permissions:
192227
contents: read
193228
defaults:
@@ -216,7 +251,8 @@ jobs:
216251
timeout-minutes: 20
217252
name: Coverage (tarpaulin + codecov)
218253
runs-on: ${{ inputs.runs-on }}
219-
if: ${{ inputs.enable_coverage && hashFiles(format('{0}/Cargo.toml', inputs.working_directory)) != '' }}
254+
needs: detect
255+
if: ${{ needs.detect.outputs.run_coverage == 'true' }}
220256
permissions:
221257
contents: read
222258
defaults:

0 commit comments

Comments
 (0)