Skip to content

fix(workflows): extract workflow_run fields via jq from $GITHUB_EVENT_PATH#27

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

fix(workflows): extract workflow_run fields via jq from $GITHUB_EVENT_PATH#27
cmeans-claude-dev[bot] merged 1 commit into
mainfrom
fix/pr-labels-ci-jq-event-path

Conversation

@cmeans-claude-dev
Copy link
Copy Markdown
Contributor

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

Summary

QA's proposed approach after PR #26's 'none' fallback also failed to fix the queue-time parse error. Diagnostic confirmed the workflow is still unregistered in GitHub's catalogue (name field on the API still reports the file path, not "PR Label Automation (CI)").

Strategy: eliminate the ${{ }} expression surface on workflow_run fields entirely. Read them from the event payload on disk via jq.

Change (+34 / −9, one file)

For both on-ci-pass and on-ci-fail:

  • Remove RUN_ID and HEAD_BRANCH from step-level env: (the only remaining ${{ github.event.workflow_run.* }} sites outside job-level if:).

  • At the top of each run: block, add:

    RUN_ID=$(jq -r '.workflow_run.id // empty' "$GITHUB_EVENT_PATH")
    HEAD_BRANCH=$(jq -r '.workflow_run.head_branch // empty' "$GITHUB_EVENT_PATH")
  • Add a defensive early-exit if RUN_ID is empty — belt-and-suspenders for the existing if: guard.

Why this might succeed where || '' / || 'none' didn't

All three of my prior attempts modified step-level env expressions. GitHub's queue-time parser kept returning the same error at run: | in both jobs. The suspected cause shifted from "missing context" to "|| fallback" to "empty-string literal" — none of those fixes worked.

The jq path routes around the expression surface entirely:

  • No ${{ }} in the workflow_run fields. Whatever GitHub's queue-time parser disliked about the env expressions can't fire here — there's no such expression.
  • $GITHUB_EVENT_PATH is always set on GitHub-hosted runners, on every event type, per official docs. Canonical way to read the raw event.
  • jq is pre-installed on all ubuntu-latest / macos-latest images. No install step.
  • Same shell-injection protection as env:. jq -r writes raw bytes to stdout; command substitution captures them into a shell variable; every subsequent use is "$HEAD_BRANCH" quoted. Byte-equivalent safety to the prior env pattern.

Unchanged

  • Job-level if: guards still reference github.event.workflow_run.* (identical to mcp-clipboard's working version). These use && and should short-circuit cleanly on non-workflow_run events.
  • All downstream logic (PR lookup, label checks, label edits) is untouched.
  • on-ci-pass and on-ci-fail happy-path behavior is identical to today's.

Decision rule

Test plan (QA)

  • yaml.safe_load on the file — clean parse (verified locally)
  • Confirm no ${{ github.event.workflow_run.* }} expressions remain outside job-level if: guards
  • jq paths use // empty so missing keys resolve to empty string, not "null"
  • Double-quoted usages of $RUN_ID and $HEAD_BRANCH preserved everywhere (no shell-injection regression)
  • on-ci-pass and on-ci-fail happy-path behavior unchanged on real workflow_run events
  • After merge: manually dispatch at https://github.com/cmeans/yt-dont-recommend/actions/workflows/pr-labels-ci.yml — report whether the queue error recurs or the run succeeds (both jobs should skip via the job-level if: on a workflow_dispatch event; if they don't skip, the defensive early-exit catches it)
  • Post-dispatch diagnostic: gh api repos/cmeans/yt-dont-recommend/actions/workflows/pr-labels-ci.yml --jq '.name' — watch for "PR Label Automation (CI)"

Caveat

Same as PRs #24 / #25 / #26 — this PR itself will stick at Awaiting CI because the fix only activates post-merge-and-seed. Expect the Dev Active toggle once more.

🤖 Generated with Claude Code

…_PATH

PR #26's 'none' fallback also didn't fix the queue-time parse error on
workflow_dispatch. Confirmed via diagnostic: pr-labels-ci.yml's API
metadata still reports name as the file path, not 'PR Label Automation
(CI)' — meaning GitHub's workflow catalogue still considers it
unregistered.

Taking QA's suggested approach: eliminate the ${{ }} expression surface
on workflow_run fields entirely. Read them from the event payload on
disk via jq instead.

Changes per job:
- Remove RUN_ID and HEAD_BRANCH from step-level env: (the only
  remaining ${{ github.event.workflow_run.* }} sites outside the
  job-level if: guards).
- Add at the top of run::
    RUN_ID=$(jq -r '.workflow_run.id // empty' "$GITHUB_EVENT_PATH")
    HEAD_BRANCH=$(jq -r '.workflow_run.head_branch // empty' "$GITHUB_EVENT_PATH")
- Add defensive early-exit if RUN_ID is empty — belt-and-suspenders for
  the existing if: guard in case someone loosens it later.

Shell-injection safety: jq -r writes raw bytes into a shell variable
via command substitution, and every subsequent use is double-quoted.
Same guarantee as the prior env: pattern.

jq and $GITHUB_EVENT_PATH are both available on all GitHub-hosted
runners (ubuntu-latest / macos-latest) — documented, not a hack.

Unchanged:
- Job-level if: guards still reference github.event.workflow_run.*.
  These are identical to mcp-clipboard's working version and should
  short-circuit cleanly on non-workflow_run events via &&. If this
  PR still hits the same queue-time parse error, the root cause is in
  the if: surface rather than the env: surface, which points toward
  reverting the whole seeding-attempt chain (#24, #25, #26, this PR).

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!

@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 Clean. on: carries workflow_dispatch + workflow_run.
No remaining step-level ${{ github.event.workflow_run.* }} Confirmed via grep -n "github.event.workflow_run" .github/workflows/pr-labels-ci.yml — all 6 remaining hits are inside job-level if: guards (lines 44–46 and 117–119), which is what we want.
jq paths use // empty Both RUN_ID and HEAD_BRANCH extractions use // empty → missing key produces zero bytes on stdout → $RUN_ID="".
Shell-injection safety preserved $HEAD_BRANCH is always double-quoted (--head "$HEAD_BRANCH"); $RUN_ID is interpolated into a URL path only (GitHub-generated numeric ID, not contributor-controlled).
Happy-path behavior jq -r '.workflow_run.id // empty' reads the same value that ${{ github.event.workflow_run.id }} would have produced. Byte-identical RUN_ID/HEAD_BRANCH values feed the exact same downstream script. Zero change on the path that promotes PRs.
pytest tests/ 259/259 pass.
ruff check src/ tests/ Clean.

jq semantics sanity-check

Ran three cases against synthetic event files:

  • Happy path ({"workflow_run":{"id":12345,"head_branch":"foo/bar"}}) → RUN_ID='12345', HEAD_BRANCH='foo/bar'
  • Missing context ({"other":"thing"}) → both empty strings ✓ (early-exit fires)
  • Injection attempt (head_branch = "foo;rm -rf /;#") → HEAD_BRANCH='foo;rm -rf /;#' captured as a single literal value; quoted usage downstream means no shell interpretation ✓

Diagnostic finding worth flagging

While validating the fix I pulled the run history for mcp-clipboard's pr-labels-ci.yml too, to cross-check:

Repo API name field workflow_run runs push/failure runs
cmeans/mcp-clipboard .github/workflows/pr-labels-ci.yml (path) 8 (success + skipped) 2
cmeans/yt-dont-recommend .github/workflows/pr-labels-ci.yml (path) 0 16

mcp-clipboard shows the exact same name == path symptom, but still dispatches workflow_run events normally. So name == path is not a reliable diagnostic for non-registration — it's just how the API reports workflows whose declared name: hasn't propagated yet. The real signal is workflow_run runs > 0, and that's what we're actually trying to unstick.

Practical consequence: after merging #27, the better post-merge check is "does a new workflow_run run (success or skipped) appear in the history after the next CI completes on a PR?" rather than "did the name field flip?" The name may or may not flip independently.

On the fix itself

This is the right structural change regardless of whether it fixes the registration issue. Moving contributor-influenced context from step-level env: (queue-time evaluation) to jq reads of $GITHUB_EVENT_PATH (step-time) is strictly safer and more forgiving, and the defensive early-exit is a reasonable belt-and-suspenders over the if: gate. If the registration still doesn't recover, the code we're left with is still better than where we started — the revert path is clean either way.

Verdict

Applying Ready for QA Signoff. Zero findings. Post-merge: try the manual dispatch as planned, but also watch the next PR's CI completion for a workflow_run entry in pr-labels-ci.yml's run history — that's the ground truth.

@cmeans cmeans added Ready for QA Signoff QA passed — ready for maintainer final review and merge and removed QA Active QA is actively reviewing; Dev should not push changes labels Apr 20, 2026
@cmeans cmeans added QA Approved Manual QA testing completed and passed and removed 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 59b9a73 into main Apr 20, 2026
36 checks passed
@cmeans-claude-dev cmeans-claude-dev Bot deleted the fix/pr-labels-ci-jq-event-path branch April 20, 2026 18:24
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