From d42d0682d43339315f80a23a9c3e8e77f2d31f52 Mon Sep 17 00:00:00 2001 From: Claude Code Bot Date: Sat, 18 Apr 2026 13:47:34 -0700 Subject: [PATCH 1/4] fix: use -f (explicit string) for minimizeComment ID variable MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Consistency with the preceding query that already uses -f for owner and name. GitHub Relay node IDs are base64-encoded and always contain non-digit characters, so the practical risk of -F type-coercing an ID to an integer is zero today — but the stated rationale in the comment above (line 262-264) recommends -f for String-typed variables, and ID values serialize the same way as String. This aligns the code with the documented pattern. Closes #44. Co-Authored-By: Claude Opus 4.7 (1M context) --- .github/workflows/claude-blocking-review.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/claude-blocking-review.yml b/.github/workflows/claude-blocking-review.yml index 521cbf3..7616449 100644 --- a/.github/workflows/claude-blocking-review.yml +++ b/.github/workflows/claude-blocking-review.yml @@ -293,7 +293,7 @@ jobs: [ -z "$id" ] && continue if gh api graphql \ -f query='mutation($id: ID!) { minimizeComment(input: {subjectId: $id, classifier: OUTDATED}) { clientMutationId } }' \ - -F id="$id" >/dev/null 2>&1; then + -f id="$id" >/dev/null 2>&1; then COUNT=$((COUNT + 1)) else echo "::warning::Failed to minimize prior review comment $id" From 83242194360fd40ec9eb7eef271caa0aa96ca642 Mon Sep 17 00:00:00 2001 From: Claude Code Bot Date: Sat, 18 Apr 2026 13:49:07 -0700 Subject: [PATCH 2/4] =?UTF-8?q?fix:=20tighten=20doc-only=20allowlist=20?= =?UTF-8?q?=E2=80=94=20remove=20*.txt,=20PR=5FTEMPLATE;=20add=20images?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Three allowlist adjustments in one commit — they share the same case block and ship together to avoid rewriting it three times. 1. Remove *.txt (resolves #48). Matched dependency manifests like requirements.txt, constraints.txt, packages.txt, which are code-adjacent and deserve review on change. `.txt` is rarely used for prose in modern repos; README/docs are conventionally .md. If a consumer genuinely has prose .txt files, they'll get a cheap review — the failure mode of skipping a requirements.txt change is significantly worse. 2. Remove .github/PULL_REQUEST_TEMPLATE.md (resolves #49). Templates can embed required security checklists, reviewer sign-offs, or label instructions. A PR that removes a required security checklist should not sail through unreviewed. ISSUE_TEMPLATE/* stays in the allowlist — those are user-facing forms with lower enforcement significance. Note: this required reordering the case statement — the explicit NON-DOC exclusion for PULL_REQUEST_TEMPLATE.md / CODEOWNERS / dependabot.yml now comes FIRST, because they'd otherwise match the general *.md / *.yml patterns that follow. The CODEOWNERS and dependabot.yml rules were already correct (they don't match *.md or the ISSUE_TEMPLATE glob) but collecting them into the same explicit exclusion block documents the "security-critical meta file" category cleanly. 3. Add image assets (resolves #45). *.png, *.jpg, *.jpeg, *.gif, *.svg, *.webp, *.ico, *.bmp. README screenshot updates, docs figures, and logo swaps don't need a paid review — they can't meet any BLOCK criterion (no runtime behavior). Conservative set — excludes *.pdf, *.psd, and other heavier binary formats. No functional change to the rest of the workflow. Co-Authored-By: Claude Opus 4.7 (1M context) --- .github/workflows/claude-blocking-review.yml | 27 ++++++++++++++++---- 1 file changed, 22 insertions(+), 5 deletions(-) diff --git a/.github/workflows/claude-blocking-review.yml b/.github/workflows/claude-blocking-review.yml index 7616449..6ad2910 100644 --- a/.github/workflows/claude-blocking-review.yml +++ b/.github/workflows/claude-blocking-review.yml @@ -150,15 +150,32 @@ jobs: while IFS= read -r f; do [ -z "$f" ] && continue case "$f" in - *.md|*.markdown|*.rst|*.txt) ;; + # Explicit NON-DOC exclusions come FIRST — they'd otherwise match + # the general *.md / *.yml patterns below. These files look meta + # but have real security impact and must not skip review: + # - PULL_REQUEST_TEMPLATE.md: can embed required security + # checklists; removing them should not bypass review. + # - CODEOWNERS: controls approval authority on code paths. + # - dependabot.yml: controls dependency sourcing. + .github/PULL_REQUEST_TEMPLATE.md|.github/CODEOWNERS|.github/dependabot.yml) + ALL_DOCS=false; NON_DOC="$f"; break ;; + # Prose documentation formats. `*.txt` was INTENTIONALLY removed + # — it matched dependency manifests like requirements.txt, + # constraints.txt, packages.txt that are code-adjacent and + # deserve review when they change. + *.md|*.markdown|*.rst) ;; LICENSE|LICENSE.*|COPYING|COPYING.*) ;; CHANGELOG|CHANGELOG.*|HISTORY|HISTORY.*|AUTHORS|NOTICE) ;; + # Build-time meta files with negligible runtime behavior impact. .gitignore|.gitattributes|.editorconfig|.mailmap) ;; - .github/ISSUE_TEMPLATE/*|.github/PULL_REQUEST_TEMPLATE.md) ;; - # FUNDING.yml is display-only; CODEOWNERS (approval authority) - # and dependabot.yml (dependency sourcing) are intentionally - # EXCLUDED — they look meta but have real security impact. + # Image assets (usually README screenshots, docs figures). + *.png|*.jpg|*.jpeg|*.gif|*.svg|*.webp|*.ico|*.bmp) ;; + # User-facing issue forms and the Sponsor button config. + .github/ISSUE_TEMPLATE/*) ;; .github/FUNDING.yml) ;; + # Doc directories at the repo root. Nested doc/ under code paths + # (e.g. src/docs/) intentionally NOT covered — if a consumer has + # nested docs they still get reviewed, conservative. docs/*|doc/*) ;; *) ALL_DOCS=false; NON_DOC="$f"; break ;; esac From 81587eff7a90872d9a95645becccb4485ae2dd5a Mon Sep 17 00:00:00 2001 From: Claude Code Bot Date: Sat, 18 Apr 2026 13:52:01 -0700 Subject: [PATCH 3/4] fix: detect renames in doc-only classification via files API MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Closes #46. Under the previous implementation, `gh pr diff --name-only` returned only the destination paths for renamed files, so a rename from `src/foo.py` → `docs/foo.md` would appear as only `docs/foo.md`, classify as doc-only, and skip review — even though the diff contains significant code deletion at the source path. Switch to the `repos/{owner}/{repo}/pulls/{pr}/files` API and emit the UNION of `previous_filename` (non-null when renamed or copied) and `filename` for every entry. The doc-only check then requires every pre-rename AND post-rename path to match the allowlist — closing the blind spot. For non-renamed files `previous_filename` is null and filtered by the jq `select(. != null)` clause, so non-rename behavior is unchanged. Verified against PRs #39, #47, #52 (all modified-only) — output is identical to the prior `--name-only` approach. An actual rename case wasn't available in recent history to test empirically, but the jq emits `previous_filename, filename` separately so the case is covered by construction: if ANY of those paths is non-doc, the final classification flips to non-doc. Co-Authored-By: Claude Opus 4.7 (1M context) --- .github/workflows/claude-blocking-review.yml | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/.github/workflows/claude-blocking-review.yml b/.github/workflows/claude-blocking-review.yml index 6ad2910..3c8c750 100644 --- a/.github/workflows/claude-blocking-review.yml +++ b/.github/workflows/claude-blocking-review.yml @@ -138,7 +138,16 @@ jobs: # security, data-loss) don't apply to prose changes. Skips saves real # money per run and eliminates the "3 flakes in a row on a doc-only # PR" pattern observed in the 2026-04-17 AAR (kebab-tax#1162). - FILES=$(gh pr diff "$PR_NUMBER" --repo "${GITHUB_REPOSITORY}" --name-only 2>/dev/null || echo "") + # + # Use the files API (not `gh pr diff --name-only`) so we get the + # pre-rename path for renamed files. A rename like src/foo.py → + # docs/foo.md would otherwise only show docs/foo.md (apparent + # doc-only) while the diff contains full code deletion. We classify + # the UNION of both paths: the PR is doc-only iff every + # previous_filename AND every filename matches the allowlist below. + FILES=$(gh api "repos/${GITHUB_REPOSITORY}/pulls/${PR_NUMBER}/files" --paginate \ + --jq '.[] | [.previous_filename, .filename] | .[] | select(. != null)' \ + 2>/dev/null | sort -u || echo "") if [ -z "$FILES" ]; then echo "::warning::Could not determine changed files — proceeding with review." echo "skip=false" >> "$GITHUB_OUTPUT" From d311d3fbaa3d086deb5661a951a1820f311398ae Mon Sep 17 00:00:00 2001 From: Claude Code Bot Date: Sat, 18 Apr 2026 13:52:47 -0700 Subject: [PATCH 4/4] fix: fall back to github.sha in SHA marker for non-pull_request triggers MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Closes #50. The marker previously interpolated `github.event.pull_request.head.sha`, which is only populated when the reusable workflow is called from a `pull_request` caller. A consumer that invokes it from `workflow_dispatch`, `push`, or another trigger would produce: with an empty `sha=` value. Downstream consumers that filter by SHA (e.g. post-push status scrapers) would then see every review for that commit as "unknown SHA" and fall through to phantom-finding behavior. Use the GitHub Actions expression `||` fallback so the marker always has a non-empty SHA. The operator returns the first truthy operand — empty strings from unpopulated nested paths like `github.event.pull_request.head.sha` evaluate falsy, so the fallback to `github.sha` fires on non-PR triggers. Behavior by trigger: - pull_request (this repo's self-review): PR head SHA, unchanged. - workflow_dispatch / push / schedule: the triggering commit SHA, which is the closest analogue to "what this review is reviewing." - scheduled / manual runs with no associated commit: github.sha is still populated with the workflow file's commit SHA, so the marker has a value rather than being empty. Co-Authored-By: Claude Opus 4.7 (1M context) --- .github/workflows/claude-blocking-review.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/claude-blocking-review.yml b/.github/workflows/claude-blocking-review.yml index 3c8c750..4dffbee 100644 --- a/.github/workflows/claude-blocking-review.yml +++ b/.github/workflows/claude-blocking-review.yml @@ -427,7 +427,7 @@ jobs: content (which already ends with the VERDICT line from Step 2). Run these three commands verbatim — do NOT omit or edit the marker: - printf '\n\n' '${{ github.event.pull_request.head.sha }}' '${{ github.run_id }}' > /tmp/review-final.md + printf '\n\n' '${{ github.event.pull_request.head.sha || github.sha }}' '${{ github.run_id }}' > /tmp/review-final.md cat /tmp/review.md >> /tmp/review-final.md gh pr comment ${{ inputs.pr_number }} --body-file /tmp/review-final.md