Skip to content

fix(ci): correct the pr-issue-linkage header's claims about the pinned reusable - #3209

Merged
kyle-sexton merged 2 commits into
mainfrom
fix/3184-pr-issue-linkage-header
Aug 23, 2026
Merged

fix(ci): correct the pr-issue-linkage header's claims about the pinned reusable#3209
kyle-sexton merged 2 commits into
mainfrom
fix/3184-pr-issue-linkage-header

Conversation

@kyle-sexton

Copy link
Copy Markdown
Contributor

Closes #3184

Summary

The header comment in .github/workflows/pr-issue-linkage.yml justified its pull_request_target trigger — and the zizmor: ignore[dangerous-triggers] suppression that points at it — with a sentence that does not describe the pinned reusable: "the reusable reads PR body metadata from the event payload only and runs no head code." Only the second half is true. The same block also misattributed the runner to "the reusable's hosted default" and described the no-issue opt-out as a literal string. This PR rewrites the header so every claim matches the reusable's actual code at the pinned SHA (7107b34, v0.14.2).

Fix

Comment text only; no functional YAML changes. Three corrections:

  • Body-read mechanism. The reusable's Load current PR body step live-refetches the body via gh api and uses the event payload only as a fallback when that call fails. Re-verified at the pinned SHA: no GH_TOKEN/GITHUB_TOKEN reaches that step (its env: carries only PR_NUMBER and FALLBACK_BODY, and neither the job nor the workflow wires a token), so the re-fetch fails auth every time and the payload fallback is the path that actually runs. The header now states this — payload-only in effect, as a side effect of the failed re-fetch, not by design — and rests the pull_request_target safety rationale on what actually makes the trigger safe: no head-branch code ever executes (the reusable checks out nothing, holds pull-requests: read / actions: read only, and passes the body through env:/GITHUB_ENV rather than splicing it into script text).
  • Runner. "Runs on the reusable's hosted default runner" replaced with the truth: the with: block pins runner: ubuntu-24.04 explicitly, which coincides with the reusable's default.
  • No-issue marker. Described as what it is: a case-insensitive regex matching the phrase "no linked issue" or "no related issue" (/\bno (?:linked|related) issue\b/i), not a literal string, with both body scans running against a body whose fenced code blocks and 4-space/tab-indented lines are blanked, inline code spans masked, and HTML-commented text discarded.

Per the issue's scope note, wiring a token into the reusable so the re-fetch succeeds is out of scope — that lives in melodic-software/ci-workflows and would be a behavior change, not a comment fix.

Verification

  • Every claim in the new header was verified directly against the reusable's source fetched at the exact pinned SHA 7107b34832a7b6db5d08d3b132621c599fbe5e50: the gh api re-fetch with FALLBACK_BODY fallback, the absence of any token in the step/job/workflow environment, the absence of any checkout step, the read-only permissions: block, the env:/GITHUB_ENV body path with random heredoc delimiter, the runner input default, the NO_ISSUE_MARKER regex, and the code/comment masking in stripRenderedHtmlComments (including the /^(?: {4}|\t)/ indented-code branch).
  • actionlint passes on the edited file; zizmor passes with the dangerous-triggers suppression still honored (No findings to report. (1 ignored, 1 suppressed)).
  • git diff -U0 filtered to non-comment lines is empty: on:, permissions:, the uses: pin, and the with: values are byte-identical to main.
  • An independent fresh-context reviewer re-verified each header claim against the pinned reusable source with the author rationale withheld: PASS on every criterion, plus two advisory phrasing findings (the "indented code blocks" wording and a dangling "Public repo:" lead-in), both applied in the second commit with linters re-run green.
  • This change is comment-only; no test can meaningfully cover it, so no test is claimed.

Related

kyle-sexton and others added 2 commits August 23, 2026 15:01
The header comment said the gate is safe under pull_request_target because
the reusable "reads PR body metadata from the event payload only and runs
no head code". Only the second half is true at the pinned SHA (7107b34,
v0.14.2): the reusable's Load current PR body step live-refetches the body
via `gh api` and uses the event payload only as a fallback. No token
reaches that step, so the re-fetch fails auth every time and the fallback
is the path that actually runs - payload-only in effect, but as a side
effect of the failed re-fetch, not by design. The safety rationale the
zizmor suppression points at now rests explicitly on what actually makes
the trigger safe: no head-branch code ever executes (nothing checked out,
read-only permissions, body passed through env/GITHUB_ENV rather than
spliced into script text).

Two adjacent claims in the same block are corrected in the same pass:

- "runs on the reusable's hosted default runner" -> the caller pins
  `runner: ubuntu-24.04` explicitly in the `with:` block; the value only
  coincides with the reusable's default.
- The no-issue marker is a case-insensitive regex matching the phrase
  "no linked issue" or "no related issue", not a literal string, and both
  body scans run with code blocks blanked, inline code spans masked, and
  HTML-commented text discarded.

Comment text only: triggers, permissions, the pinned SHA, and the `with:`
values are unchanged. actionlint and zizmor both pass on the edited file,
with the dangerous-triggers suppression still honored.

Closes #3184

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01XtbWChCVfUWAv1Pi5Qk2hA
Independent review confirmed every claim in the rewritten header against
the pinned reusable but flagged two phrasings a maintainer could misread:

- "indented code blocks blanked" suggested the CommonMark indented-code
  construct, but the masking pass blanks any line matching
  /^(?: {4}|\t)/ - including a 4-space-indented nested list item that is
  not code. The header now says "any 4-space- or tab-indented line".
- "Public repo:" no longer connected to the sentence that followed once
  the hosted-default-runner claim was replaced with the explicit pin.
  The lead-in now states why visibility matters (GitHub-hosted runners
  are free for public repositories, matching ci.yml's phrasing) before
  naming the pin.

Still comment text only; actionlint and zizmor both remain green with
the dangerous-triggers suppression honored.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01XtbWChCVfUWAv1Pi5Qk2hA
@claude

claude Bot commented Aug 23, 2026

Copy link
Copy Markdown
Contributor

Claude finished @kyle-sexton's task in 1m 0s —— View job


Security Review

  • Gather context (PR diff, changed file, custom instructions)
  • Run skip-gate check
  • Review diff for security-relevant surface
  • Post findings

No security issues found.

This PR touches a single file, .github/workflows/pr-issue-linkage.yml, and the change is comment-only: git diff 7dea5a8..HEAD -- .github/workflows/pr-issue-linkage.yml shows every added/removed line falls inside the header comment block (lines 3–26). The functional YAML — on:, permissions: {}, the concurrency: block, the job's permissions:, the pinned uses: SHA, and the with: values — is byte-identical to the pre-PR revision, matching the PR description's own git diff -U0 verification.

Since there is no security-relevant surface change (skip-gate criterion 2 in /review:security-review), no vulnerability hunt applies here — nothing new to assess for injection, permission-widening, unpinned/loosened supply-chain risk, or unsafe pull_request_target/workflow_run patterns. The preexisting pull_request_target trigger, the read-only permissions: blocks, and the pinned reusable SHA (7107b34832a7b6db5d08d3b132621c599fbe5e50 / v0.14.2) are all unchanged by this PR.

As a documentation-accuracy note (not a security finding): the corrected header's safety rationale — no head-branch code executes because the reusable checks out nothing, holds only pull-requests: read/actions: read, and passes the PR body via env:/GITHUB_ENV rather than into script text — is consistent with what's visible in this file and does not introduce any inaccurate claim that would encourage an unsafe pattern later.

@github-actions

Copy link
Copy Markdown

Last security-reviewed head: b07b96ed34c137459c4794a2e1e0c9fe896d5a7b. On the next push, the relevance gate compares only the commits since this SHA; delete this comment to force a full re-review.

@claude

claude Bot commented Aug 23, 2026

Copy link
Copy Markdown
Contributor

Claude finished @kyle-sexton's task in 2m 26s —— View job


Claude Code Review

  • Gather context (PR diff, changed files, CLAUDE.md/REVIEW.md)
  • Understand the request (code review only, comment-only YAML change)
  • Review .github/workflows/pr-issue-linkage.yml header changes against the pinned reusable's actual behavior
  • Post findings (inline + summary)

Summary

No blocking findings. This is a comment-only rewrite of the header block in .github/workflows/pr-issue-linkage.yml; the security lane (claude-security-review) already ran and passed on this head, so I kept this review scoped to correctness/maintainability per the code-review lane split.

What I independently verified myself:

  • git diff origin/main HEAD -- .github/workflows/pr-issue-linkage.yml — the on:, permissions:, concurrency:, job permissions:, uses: pin, and with: block are byte-identical to main; only the comment block (L3–L26) changed. Confirms the PR's "comment-only, no functional YAML changes" claim.
  • The four section names cited in the comment (## Summary, ## Fix, ## Verification, ## Related) and the "No linked issue" phrasing match .github/pull_request_template.md.
  • The new claim "holds read-only pull-request/actions permissions only" (L14) matches the job's own permissions: block in this file (L44–L46: pull-requests: read, actions: read).
  • The required-check context claim (pr-issue-linkage / pr-issue-linkage, L23) matches this file's name: (L1) and job id (L43).
  • CI on this head is green (ci, pr-issue-linkage, claude-security-review all success).

What I could not independently verify: the claims about the pinned reusable's actual implementation at 7107b34 in melodic-software/ci-workflows — the gh api re-fetch/FALLBACK_BODY fallback, the absence of any token in that step's env:, the absence of a checkout step, and the exact code/comment-masking regex behavior. This review lane doesn't have cross-repo/network access to that external repo, so I can't confirm those beyond what's stated in the PR description and its cited independent fresh-context reviewer. Flagging this as a scope limitation rather than a defect — the PR's stated verification process (direct source read at the pinned SHA plus a separate adversarial reviewer pass) is reasonable for a comment-only change like this, and I have no evidence contradicting it, but I want it explicit that I did not re-derive it myself.

No inline comments — I found no anchorable code defect to flag.

@github-actions

Copy link
Copy Markdown

Claude has reviewed this PR 1 time. The lane skips further automatic reviews after 5; deleting this comment resets the count.

@kyle-sexton
kyle-sexton merged commit c1ed704 into main Aug 23, 2026
50 checks passed
@kyle-sexton
kyle-sexton deleted the fix/3184-pr-issue-linkage-header branch August 23, 2026 20:08
kyle-sexton added a commit that referenced this pull request Aug 24, 2026
…ions (0.55.11) (#3217)

Closes #3206

## Summary

The shared PR-body linkage validator only checked a closing keyword and
`## Related`, so both local pre-checks allowed bodies the pinned
`pr-issue-linkage` reusable rejects. Local gates now require the same
four contract sections CI does.

## Fix

`pr-linkage-validator.sh` generalizes `related_section()` into
`section_content()` over `Summary`, `Fix`, `Verification`, and
`Related`, using the existing heading-level semantics (a nested `###` is
content). Each missing or empty section is its own `LINKAGE_PROBLEMS`
entry. Both hook surfaces pick this up from the shared core; their
blocked-message remedies now list all four sections so following them
produces a body CI accepts. The no-issue-marker path and
`update_pull_request` without `body` are unchanged.

## Verification

`bash plugins/source-control/hooks/pr-body-linkage-gate.test.sh` —
passed: 137 failed: 0

`bash plugins/source-control/hooks/pr-linkage-mcp-gate.test.sh` —
pass=25 fail=0

New cases: the #3205 body (linkage + Summary + Verification + Related,
no Fix) is blocked; a nested `###` inside each required section is
content. `check-changelog-parity.sh --check-bump origin/main` passes
(0.55.6).

## Related

- #3205 — the PR whose `pr-issue-linkage` failure is the reproduction
- #3209 — comment-only header correction on the same reusable pin; no
functional overlap
- `.github/workflows/pr-issue-linkage.yml` — pins the upstream reusable
at v0.14.2

---------

Co-authored-by: Kyle Sexton <kyle-sexton@users.noreply.github.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.

fix(ci): pr-issue-linkage header misstates how the reusable reads the PR body

1 participant