fix(workflows): guard pr-labels-ci.yml env against missing workflow_run context#25
Conversation
…un context Attempting to seed the workflow via workflow_dispatch on main after PR #24 merged failed with: Failed to queue workflow run: Invalid Argument - failed to parse workflow: (Line: 55, Col: 14): An expression was expected, (Line: 111, Col: 14): An expression was expected Lines 55 and 111 are `run: |` in both jobs. The real problem is a few lines earlier: the step-level env: references `github.event.workflow_run.id` and `github.event.workflow_run.head_branch`, which are absent when the workflow is triggered via workflow_dispatch (or a validation push). GitHub evaluates step-level env: expressions at queue time — before the job-level `if:` has a chance to skip the job — so the missing context is fatal even though the logic would skip cleanly. This is also the likely explanation for the push-event startup failures in this workflow's run history: every push GitHub validates has been failing to resolve these env: expressions, which is what prevented the file from being registered in the workflow catalogue and made the workflow_run listener never fire. Fix: add `|| ''` null-coalesce fallbacks to both env: expressions. On workflow_run events (the happy path), the fallback is never exercised — behaviour is unchanged. On workflow_dispatch and push events, the env: assignments cleanly resolve to empty strings, the job-level `if:` fails as designed, and the job skips — producing a successful run that finally registers the file with GitHub. Cascade: cmeans/mcp-clipboard should pick up the same fallbacks. They don't exercise workflow_dispatch today, so the bug is latent there, but the guard is strictly more defensive and preserves the verbatim contract. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Codecov Report✅ All modified and coverable lines are covered by tests. 📢 Thoughts on this report? Let us know! |
cmeans
left a comment
There was a problem hiding this comment.
[QA] Review — zero findings, Ready for QA Signoff.
Verified in this session
| Check | Result |
|---|---|
YAML parse of pr-labels-ci.yml |
Clean; both jobs on-ci-pass and on-ci-fail resolve. |
| Diff shape | Exactly 4 lines changed: 2 per job, identical || '' fallback added to RUN_ID and HEAD_BRANCH. No other content touched. |
workflow_run happy-path behavior |
When github.event.workflow_run.id exists (i.e. CI-completion dispatches), it's truthy, so || '' never fires. RUN_ID and HEAD_BRANCH resolve to the real values, and both env vars are identical to today. Zero behavior change on the path that actually promotes PRs. |
workflow_dispatch / push path |
With workflow_run context absent, the .id / .head_branch lookups return null, || '' converts to empty strings, env vars set successfully, queue-time parse succeeds. Job-level if: conditions (which reference .conclusion == 'success' etc.) evaluate false against null and skip the job. Clean successful-skipped run. |
pytest tests/ |
259/259 pass (non-code change). |
ruff check src/ tests/ |
All checks passed! |
Root cause — independently plausible
The Invalid Argument - failed to parse workflow: ... An expression was expected error pointing at run: \| lines is a recognized GitHub pattern for step-level env: expressions that resolve to null. GHA evaluates step-level env at queue time and the null value is rejected as an invalid env value (the error message reports the column of the next line, which is why it looked like the run: block itself was the problem). The \|\| '' fallback is the documented, zero-risk hardening pattern for this class of issue.
Dev's secondary claim — that this also explains the pre-#24 event=push conclusion=failure streak in this workflow's run history — is consistent: every commit touching workflow files triggers a GHA validation push, which would hit the same queue-time env resolution. If that's right, the fix should also end the startup-failure streak going forward. Directly observable post-merge.
Verdict
Applying Ready for QA Signoff. Zero findings.
Post-merge action (for you)
Same playbook as #24 called out:
- Actions → PR Label Automation (CI) → Run workflow →
main→ Run workflow. Expect ~5s, both jobs skipped, status success. - Confirm
actions/workflowsreportsname = "PR Label Automation (CI)"rather than the file path — registration proof. - Next PR should auto-promote
Awaiting CI→Ready for QAon CI pass without the Dev Active toggle.
Worth also glancing at the workflow's run history afterwards to see if the push-event startup-failure streak actually stops.
Cascade
Extended the existing tracker — cmeans/mcp-clipboard#89 — to cover this \|\| '' fallback alongside the workflow_dispatch addition. Same commit can ship both to mcp-clipboard; they're the same underlying concern (make pr-labels-ci.yml safe to run on non-workflow_run events).
…actual bug) Root cause for the workflow_dispatch parse failures that defeated PRs #24, #25, #26, and #27: GitHub Actions substitutes '\${{ ... }}' expressions inside run: blocks *before* the shell sees them, including sequences inside shell comments. Three comments in this file contained the literal string '\${{ }}' (empty expression). GHA's queue-time parser tried to evaluate an empty expression and bailed with 'An expression was expected', always pointing at 'run: |' (col 14) because that's the parent scope. Diagnostic that confirmed this: the reported line numbers shifted between my earlier attempts (55/111 -> 53/126 after the jq refactor). The error position is file-structure-dependent; col 14 is always the '|' of 'run: |'. Meanwhile the literal '\${{ }}' was identical across every version of the file since the security-hardening cascade (mcp-clipboard#87), which is why every '|| X' fallback experiment missed — the fallbacks were on the wrong expressions. Fix: rewrite the three comments to describe the concept without the literal '\${{ }}' characters. Zero logic change. Why this only surfaced on workflow_dispatch (not on normal workflow_run firings): the normal workflow_run code path apparently tolerates empty expressions where the queue-time parser for workflow_dispatch does not. mcp-clipboard has the same latent bug — it just never tripped it because nobody dispatched manually. Cascade: the same fix needs to land on cmeans/mcp-clipboard's file to preserve verbatim parity and to unblock manual dispatch there as well. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Summary
Follow-up to PR #24. Attempting to manually dispatch
pr-labels-ci.ymlonmainafter PR #24 merged failed with:Root cause
Lines 55 and 111 are
run: |— the error message points a line or two late. The actual problem is the step-levelenv:blocks above them:github.event.workflow_runis only populated forworkflow_runevents. Forworkflow_dispatch(and for the validation pushes GitHub runs on every commit touching workflow files), that path is absent. GitHub evaluates step-levelenv:expressions at queue time — before the job-levelif:gate is checked — so the missing context aborts the whole queue operation.This is almost certainly the explanation for the pre-#24 push startup-failures in this workflow's run history too: every commit that modified workflow files triggered a GitHub validation push, the env expressions couldn't resolve, the run failed at queue time, and the workflow was never registered in GitHub's catalog. That's why
workflow_rundispatches never routed to it.Fix
Add null-coalesce fallbacks on the two workflow_run references, in both jobs:
Behavior matrix:
workflow_run(CI success on PR branch).github.event.workflow_run.idresolves to a real number; the fallback is never exercised. Job-levelif:passes; on-ci-pass promotes the PR toReady for QA. Unchanged from today's happy path.workflow_run(CI failure). Same — real number resolves, fallback unused.on-ci-failbranch marksCI Failed. Unchanged.workflow_dispatch(manual seeding).github.event.workflow_run.idresolves to null; the fallback yields empty string; env vars are set to empty strings; job-levelif:fails (null is not equal to'success'); both jobs skip. Run completes as "successful skipped" — which is exactly what registers the workflow with GitHub's catalog.push(validation on commits that touch workflow files). Same as workflow_dispatch — null resolves cleanly, jobs skip. This should end the streak of startup-failure entries in the workflow's run history.Post-merge plan
After merge, try the manual dispatch again:
main→ Run workflowExpected: both jobs skip, run completes in ~5 seconds with status "success" (both jobs shown as "skipped"). After that, the workflow's sidebar name should start resolving to "PR Label Automation (CI)" instead of the raw file path, and
workflow_rundispatches should finally route there.Cascade
cmeans/mcp-clipboardhas the same expressions without fallbacks. The bug is latent there because they've never dispatched manually, but the fallback is strictly more defensive (zero behavior change on the working path) and preserves the verbatim-from-mcp-clipboard contract. Recommend cascading as a follow-up PR.Test plan (QA)
yaml.safe_loadon the file — clean parseworkflow_runhappy pathAwaiting CI→Ready for QAon CI success without the Dev Active toggleCaveat
This PR will itself stick at
Awaiting CIsince it doesn't fix the already-stuck registration — only post-merge does that. Expect the Dev Active toggle dance one more time.🤖 Generated with Claude Code