Skip to content

fix(rust-ci-reusable): wrap bare job-level if: in ${{ }} so GH parses (closes #322)#334

Merged
hyperpolymath merged 1 commit into
mainfrom
fix/322-rust-ci-reusable-bare-if-job-level
Jun 1, 2026
Merged

fix(rust-ci-reusable): wrap bare job-level if: in ${{ }} so GH parses (closes #322)#334
hyperpolymath merged 1 commit into
mainfrom
fix/322-rust-ci-reusable-bare-if-job-level

Conversation

@hyperpolymath
Copy link
Copy Markdown
Owner

Summary

`rust-ci-reusable.yml` lines 109 and 149 used a bare `if:` expression at job level:

```yaml
if: hashFiles(format('{0}/Cargo.toml', inputs.working_directory)) != ''
```

GH Actions silently fails to parse the entire CALLER workflow when this exact shape (hashFiles(format(inputs.X)) bare at job-level) is reached in a reusable. Caller manifests as 0s-duration "completed failure" with the workflow name reported as the path (.github/workflows/rust-ci.yml) instead of the YAML name: field — GitHub's "This run likely failed because of a workflow file issue" diagnostic.

Root-cause investigation

Run on panic-attack#95 (experiment branch, 5 commits), tracking the parsed workflow name field via API:

commit caller pin caller shape reusable parsed name result
1d0a8b9 @cc5a372a original thin wrapper rust-ci-reusable.yml path-fallback ❌ fail
d022c8f @main original thin wrapper rust-ci-reusable.yml path-fallback ❌ fail
02c6753 @main governance.yml shape rust-ci-reusable.yml path-fallback ❌ fail
6250e19 @main governance.yml shape governance-reusable.yml 'Rust CI' parses (fails at runtime, expected)
cb23224 @main governance.yml verbatim clone governance-reusable.yml 'Rust CI' parses

Falsifies #322 hypotheses 1+2+3:

  • Hypothesis 1 (caller structure) — falsified: changing the caller's YAML to match governance.yml verbatim did NOT fix.
  • Hypothesis 2 (SHA resolution) — falsified: @main floating ref ALSO failed.
  • Hypothesis 3 (workflow_id cache) — falsified: a fresh `workflow_id` 287101574 (created via filename rename) ALSO failed.

Root cause: hypothesis 4 (new) — when the caller's `uses:` resolves to a reusable whose first job has a bare `if: hashFiles(format(...))` at job level, GH's expression evaluator rejects the resolution at workflow-parse time. The audit + coverage jobs (lines 187, 215) were always wrapped in `${{ }}` and worked; the check + test jobs were not.

The fix

Two-line if: wrapping. Diff:

```diff

  • if: hashFiles(format('{0}/Cargo.toml', inputs.working_directory)) != ''
  • if: ${{ hashFiles(format('{0}/Cargo.toml', inputs.working_directory)) != '' }}
    ```

Estate impact

Once this merges, every caller of `rust-ci-reusable.yml` should immediately start parsing on its next push — no caller-side action needed for callers using `@main` floating refs. SHA-pinned callers will need to bump the pin to a commit at-or-after this merge.

Follow-up

  • 43+ panic-attack-tracked callers using SHA-pinned rust-ci-reusable.yml — bumping their pins to the new commit could be a fan-out, but a no-op SHA bump is sufficient once `@main` resolution is confirmed clean.
  • Audit other reusables (`governance-reusable.yml`, `secret-scanner-reusable.yml`, etc.) for the same anti-pattern via grep: `^\s*if: [^$]` followed by `hashFiles\|format\|inputs\.` — none found in governance-reusable on inspection but worth a sweep.

Refs

🤖 Generated with Claude Code

…ns parses (fixes #322)

GH Actions silently fails to parse the entire caller workflow when the
reusable's job-level \`if:\` is a bare expression containing
\`hashFiles(format('{0}/...', inputs.X))\`. The parse failure shows up
estate-wide as 0s-duration "completed failure" runs since 2026-05-26
across 43+ callers, with the caller's workflow name reported as the
path (\`.github/workflows/rust-ci.yml\`) instead of the YAML \`name:\` field
— GitHub's tell-tale "workflow file issue".

The audit and coverage jobs (lines 187, 215) already used \`\${{ ... }}\`
wrapping and parsed cleanly. The check (109) and test (149) jobs used
the bare form because, viewed in isolation, the YAML spec permits both.

Empirically confirmed via 5-commit experiment on panic-attack#95:

  caller @cc5a372a → fail (name=path)        rust-ci-reusable.yml
  caller @main     → fail (name=path)        rust-ci-reusable.yml
  caller w/governance.yml shape → fail (name=path)  rust-ci-reusable.yml
  caller pointing at governance-reusable → PASS (name="Rust CI")
  caller verbatim clone of governance.yml → PASS

Only the swap to governance-reusable.yml made the parse succeed → the
bug is in rust-ci-reusable.yml specifically. The only structural diff
between governance-reusable.yml and rust-ci-reusable.yml in the \`if:\`
shape is exactly this bare-vs-wrapped split, and the bare lines in
rust-ci-reusable.yml had a \`format()\` + \`inputs.X\` substitution that
GH's expression parser rejects at job-level without the \`\${{ }}\` hint.

Closes #322.
Falsifies hypotheses 1+2+3 from the issue (caller structure, SHA
resolution, workflow_id cache); root cause is hypothesis 4 (bare \`if:\`
with \`hashFiles(format(inputs.X))\` at job level).

Verified locally:
- yamllint clean
- yaml.safe_load parses both pre- and post-fix
- the experiment branch panic-attack#95 last commit (governance-shape
  pointing at governance-reusable.yml) had name='Rust CI' parsed
  correctly

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@github-actions
Copy link
Copy Markdown

github-actions Bot commented Jun 1, 2026

🔍 Hypatia Security Scan

Findings: 201 issues detected

Severity Count
🔴 Critical 64
🟠 High 43
🟡 Medium 94

⚠️ Action Required: Critical security issues found!

View findings
[
  {
    "reason": "Action  (for the check script)\n        uses: actions/checkout@de needs attention",
    "type": "unpinned_action",
    "file": "governance-reusable.yml",
    "action": "pin_sha",
    "rule_module": "workflow_audit",
    "severity": "medium"
  },
  {
    "reason": "Action  (for the check script)\n        uses: actions/checkout@de needs attention",
    "type": "unpinned_action",
    "file": "governance-reusable.yml",
    "action": "pin_sha",
    "rule_module": "workflow_audit",
    "severity": "medium"
  },
  {
    "reason": "Issue in affinescript-verify.yml",
    "type": "missing_timeout_minutes",
    "file": "affinescript-verify.yml",
    "action": "flag",
    "rule_module": "workflow_audit",
    "severity": "medium"
  },
  {
    "reason": "Issue in boj-build.yml",
    "type": "missing_timeout_minutes",
    "file": "boj-build.yml",
    "action": "flag",
    "rule_module": "workflow_audit",
    "severity": "medium"
  },
  {
    "reason": "Issue in casket-pages.yml",
    "type": "missing_timeout_minutes",
    "file": "casket-pages.yml",
    "action": "flag",
    "rule_module": "workflow_audit",
    "severity": "medium"
  },
  {
    "reason": "Issue in casket-pages.yml",
    "type": "missing_timeout_minutes",
    "file": "casket-pages.yml",
    "action": "flag",
    "rule_module": "workflow_audit",
    "severity": "medium"
  },
  {
    "reason": "Issue in changelog-reusable.yml",
    "type": "missing_timeout_minutes",
    "file": "changelog-reusable.yml",
    "action": "flag",
    "rule_module": "workflow_audit",
    "severity": "medium"
  },
  {
    "reason": "Issue in codeql-reusable.yml",
    "type": "missing_timeout_minutes",
    "file": "codeql-reusable.yml",
    "action": "flag",
    "rule_module": "workflow_audit",
    "severity": "medium"
  },
  {
    "reason": "Issue in codeql.yml",
    "type": "missing_timeout_minutes",
    "file": "codeql.yml",
    "action": "flag",
    "rule_module": "workflow_audit",
    "severity": "medium"
  },
  {
    "reason": "Issue in deno-ci-reusable.yml",
    "type": "missing_timeout_minutes",
    "file": "deno-ci-reusable.yml",
    "action": "flag",
    "rule_module": "workflow_audit",
    "severity": "medium"
  }
]

Powered by Hypatia Neurosymbolic CI/CD Intelligence

@hyperpolymath hyperpolymath merged commit d73c93e into main Jun 1, 2026
20 checks passed
@hyperpolymath hyperpolymath deleted the fix/322-rust-ci-reusable-bare-if-job-level branch June 1, 2026 18:37
hyperpolymath added a commit that referenced this pull request Jun 1, 2026
…-pattern as #322/#334)

elixir-ci-reusable.yml:105 had the same bare-`if:`-at-job-level pattern
that #334 fixed in rust-ci-reusable.yml. Surfaced via the sweep recipe
documented in standards#334:

    grep -nE '^\s+if: [^$]+(hashFiles|format|inputs\.)' .github/workflows/*-reusable.yml

The mix.exs guard is structurally identical to the Cargo.toml guard
that broke 43+ Rust callers — same fix.

Follow-up audit: same grep found ZERO additional hits in other
reusables (governance/secret-scanner/scorecard/mirror/casket/dogfood).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
hyperpolymath added a commit that referenced this pull request Jun 1, 2026
…-pattern as #322/#334) (#336)

## Summary

Same root cause as #322 / #334 — \`elixir-ci-reusable.yml:105\` had a
bare \`if:\` at job level containing \`hashFiles(format('{0}/mix.exs',
inputs.working_directory))\`. Surfaced via the sweep recipe from #334:

\`\`\`bash
grep -nE '^\\s+if: [^\$]+(hashFiles|format|inputs\\.)'
.github/workflows/*-reusable.yml
\`\`\`

Only hit — every other reusable is clean. Same 2-line `\${{ }}` wrap.

## Test plan

- [x] Sweep recipe returns no remaining hits across reusables
- [x] yamllint clean
- [x] Pattern matches #334's exact diff shape

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
hyperpolymath added a commit to hyperpolymath/panic-attack that referenced this pull request Jun 1, 2026
…st-ci.yml from baseline-red) (#97)

## Summary

\`standards#334\` (merged 2026-06-01T18:37Z) wrapped the bare job-level
\`if:\` in \`rust-ci-reusable.yml\` that GH Actions had been silently
rejecting since 2026-05-26 — the parse failure tracked in standards#322
that produced 0s-duration "completed failure" runs across the estate.

Bumps panic-attack's pin from \`cc5a372a\` (pre-fix) to \`d7c3b8f7\`
(current standards/main HEAD; includes #334 and follow-up
canonical-wrapper SHA sweep #335).

## Test plan

- [x] SHA exists on standards/main: \`gh api
/repos/hyperpolymath/standards/commits/d7c3b8f75d70ab640e6f37a4c8f9e6e8f2b6df56\`
returns 200
- [x] Includes standards#334 fix in its history
- [ ] On merge: rust-ci.yml run should report \`name='Rust CI'\`
(parsed) instead of \`name='.github/workflows/rust-ci.yml'\` (path
fallback)
- [ ] Jobs (\`check\` + \`test\`) should actually execute rather than
0s-fail

## Refs

- Closes panic-attack#84 retroactively (orphan-SHA hypothesis was
incorrect; real bug fixed at source).
- Refs hyperpolymath/standards#322, hyperpolymath/standards#334.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

estate-wide rust-ci.yml 0s-duration parse failures on 43+ callers since 2026-05-26 (post-thin-wrapper-conversion)

1 participant