Skip to content

fix(workflows): guard pr-labels-ci.yml env against missing workflow_run context#25

Merged
cmeans-claude-dev[bot] merged 1 commit into
mainfrom
fix/pr-labels-ci-dispatch-context
Apr 20, 2026
Merged

fix(workflows): guard pr-labels-ci.yml env against missing workflow_run context#25
cmeans-claude-dev[bot] merged 1 commit into
mainfrom
fix/pr-labels-ci-dispatch-context

Conversation

@cmeans-claude-dev
Copy link
Copy Markdown
Contributor

@cmeans-claude-dev cmeans-claude-dev Bot commented Apr 20, 2026

Summary

Follow-up to PR #24. Attempting to manually dispatch pr-labels-ci.yml 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

Root cause

Lines 55 and 111 are run: | — the error message points a line or two late. The actual problem is the step-level env: blocks above them:

env:
  GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
  REPO: ${{ github.repository }}
  RUN_ID: ${{ github.event.workflow_run.id }}
  HEAD_BRANCH: ${{ github.event.workflow_run.head_branch }}

github.event.workflow_run is only populated for workflow_run events. For workflow_dispatch (and for the validation pushes GitHub runs on every commit touching workflow files), that path is absent. GitHub evaluates step-level env: expressions at queue time — before the job-level if: 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_run dispatches never routed to it.

Fix

Add null-coalesce fallbacks on the two workflow_run references, in both jobs:

RUN_ID: ${{ github.event.workflow_run.id || '' }}
HEAD_BRANCH: ${{ github.event.workflow_run.head_branch || '' }}

Behavior matrix:

  • workflow_run (CI success on PR branch). github.event.workflow_run.id resolves to a real number; the fallback is never exercised. Job-level if: passes; on-ci-pass promotes the PR to Ready for QA. Unchanged from today's happy path.
  • workflow_run (CI failure). Same — real number resolves, fallback unused. on-ci-fail branch marks CI Failed. Unchanged.
  • workflow_dispatch (manual seeding). github.event.workflow_run.id resolves to null; the fallback yields empty string; env vars are set to empty strings; job-level if: 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:

Expected: 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_run dispatches should finally route there.

Cascade

cmeans/mcp-clipboard has 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_load on the file — clean parse
  • Diff shows exactly two lines changed in each of the two jobs (4 total line changes): adding the null-coalesce fallback
  • No behavior change on workflow_run happy path
  • After merge + manual workflow_dispatch: run completes successfully (both jobs skipped), workflow gets a clean registration
  • Next PR after registration: auto-promotes Awaiting CIReady for QA on CI success without the Dev Active toggle

Caveat

This PR will itself stick at Awaiting CI since 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

…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>
@github-actions github-actions Bot added the Awaiting CI Dev complete, waiting for CI to pass before QA label Apr 20, 2026
@cmeans-claude-dev cmeans-claude-dev Bot added Dev Active Developer is actively working on this PR; QA should not start and removed Dev Active Developer is actively working on this PR; QA should not start labels Apr 20, 2026
@github-actions github-actions Bot added Ready for QA Dev work complete — QA can begin review and removed Awaiting CI Dev complete, waiting for CI to pass before QA labels Apr 20, 2026
@codecov-commenter
Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.

📢 Thoughts on this report? Let us know!

Copy link
Copy Markdown
Owner

@cmeans cmeans left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM

@cmeans cmeans added the QA Active QA is actively reviewing; Dev should not push changes label Apr 20, 2026
@github-actions github-actions Bot removed the Ready for QA Dev work complete — QA can begin review label Apr 20, 2026
Copy link
Copy Markdown
Owner

@cmeans cmeans left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[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:

  1. Actions → PR Label Automation (CI)Run workflowmainRun workflow. Expect ~5s, both jobs skipped, status success.
  2. Confirm actions/workflows reports name = "PR Label Automation (CI)" rather than the file path — registration proof.
  3. Next PR should auto-promote Awaiting CIReady for QA on 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).

@cmeans cmeans added Ready for QA Signoff QA passed — ready for maintainer final review and merge QA Approved Manual QA testing completed and passed and removed QA Active QA is actively reviewing; Dev should not push changes Ready for QA Signoff QA passed — ready for maintainer final review and merge labels Apr 20, 2026
@cmeans-claude-dev cmeans-claude-dev Bot merged commit fdbbdbe into main Apr 20, 2026
36 checks passed
@cmeans-claude-dev cmeans-claude-dev Bot deleted the fix/pr-labels-ci-dispatch-context branch April 20, 2026 16:48
cmeans-claude-dev Bot pushed a commit that referenced this pull request Apr 20, 2026
…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>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

QA Approved Manual QA testing completed and passed

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants