Files
windmill/.github/workflows/pr-ready-review.yml
Alexander PetricandClaude Opus 5 40476e7be9 ci: skip the AI reviews and the Discord notice on Dependabot PRs (#11307)
* ci: skip the AI reviews and the Discord notice on Dependabot PRs

They already do not review them, they just fail while doing so. The Claude
action rejects a bot actor outright ("Workflow initiated by non-human actor"),
and the Codex and Pi jobs find no API key because GitHub gives Dependabot-
triggered runs a separate secret scope from Actions. Verified on #11302: zero
reviews posted, the only comment is a Cloudflare deployment notice, while
codex-review and pi-review both reported success.

So every Dependabot PR carried two permanently red checks that meant nothing,
which is the worst kind of signal — it buries a Dependabot PR that genuinely is
broken, and a green tick that means "skipped" reads exactly like one that means
"looked and approved".

Skipping states it honestly. The workflow_call branch is untouched, so a review
can still be requested on a specific bot PR when the diff deserves one, which is
worth doing for a grouped security update that swaps a cipher or drops a parser
rather than just moving a version.

The Discord notice is excluded for the same reason plus its own: nobody wants a
forum thread per lockfile bump.

Not fixed here, deliberately: making these actually review bot PRs would need
the org's review credentials copied into the Dependabot secret scope, which is a
wider grant than this is worth given the PRs in question are lockfile diffs.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

* ci: gate the close-time Discord job too, and trim the comments

Both reviewers caught the same gap: merge_success_emoji runs on every `closed`
event with no exclusion, and the reusable workflow it calls exits 1 when it
cannot find a thread. Since open_thread no longer creates one for Dependabot,
the failure would have moved from open-time to merge-time rather than going
away. Gated to match.

The comments are cut from eight lines to two per file. AGENTS.md:205 asks for
constraints in <=4 lines, stated once, describing the code as it is — mine
narrated the drafting history and argued the change to a reviewer, both of which
belong in the PR description. Also drops "bot-authored", which overstated a
condition that only covers dependabot[bot].

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-23 10:59:50 +00:00

212 lines
9.3 KiB
YAML

name: Claude Auto Review
on:
pull_request:
types: [ready_for_review, opened]
workflow_call:
inputs:
pr_number:
description: 'PR number to review'
required: true
type: number
extra_prompt:
description: 'Additional reviewer instructions appended to the standard review prompt'
required: false
type: string
default: ''
triggered_by:
description: 'GitHub username that triggered this review (for audit only)'
required: false
type: string
default: ''
secrets:
CLAUDE_CODE_OAUTH_TOKEN:
required: true
WINDMILL_EE_PRIVATE_ACCESS:
required: false
concurrency:
group: claude-review-${{ inputs.pr_number || github.event.pull_request.number }}
cancel-in-progress: true
jobs:
auto-review:
runs-on: ubuntu-latest
# A non-fork PR (head.repo.fork == false) can only be opened by someone with push
# access to this repo, so fork==false already enforces write access. Do NOT re-add
# an author_association gate: the pull_request webhook payload reports private org
# members as CONTRIBUTOR/NONE (only public members show as MEMBER), which silently
# skips auto-review for every private member.
# Dependabot-triggered runs get a separate secret scope, so this job has no API
# key and cannot review. workflow_call stays open for an explicit request.
if: |
github.event_name == 'workflow_call' ||
(
(github.event.pull_request.draft == false || github.event.pull_request.ready_for_review == true) &&
github.event.pull_request.head.repo.fork == false &&
github.event.pull_request.user.login != 'dependabot[bot]'
)
permissions:
contents: read
pull-requests: read
id-token: write
steps:
# An agent-driven PR flips to ready only after a clean /review round on a
# draft, marked by an author comment naming the head SHA (pr skill, "Review
# rounds"). Re-reviewing that same head on ready_for_review is redundant.
# The marker alone is author attestation, so also require reviewer evidence:
# a Codex review (posted by github-actions[bot], not forgeable by the author)
# that predates the marker and carries a non-blocking verdict. Comment-
# triggered (workflow_call) and opened runs never skip. Keep the three
# copies of this check in sync (pr-ready-review / codex-pr-review /
# pi-pr-review); a shared local action would need the repo checked out
# before the check, which the codex/pi fork paths deliberately avoid.
- name: Check clean-round marker
id: marker
if: github.event_name == 'pull_request' && github.event.action == 'ready_for_review'
env:
GH_TOKEN: ${{ github.token }}
REPO: ${{ github.repository }}
PR_NUMBER: ${{ github.event.pull_request.number }}
HEAD_SHA: ${{ github.event.pull_request.head.sha }}
PR_AUTHOR: ${{ github.event.pull_request.user.login }}
run: |
# Fetch failures fail open (skip=false): an API hiccup must run the
# review, never skip it or fail the job.
COMMENTS=$(gh api "repos/$REPO/issues/$PR_NUMBER/comments?per_page=100" --paginate | jq -s '[.[][]]') || COMMENTS='[]'
MARKER_TIME=$(jq -r --arg author "$PR_AUTHOR" --arg marker "✅ Review round clean @ $HEAD_SHA" \
'[.[] | select(.user.login == $author) | select(.body | contains($marker)) | .created_at] | min // empty' <<<"$COMMENTS")
CODEX_VERDICT=''
if [ -n "$MARKER_TIME" ]; then
# Only Codex evidence that predates the marker counts: the ready-
# triggered Codex run itself posts after the flip and must not vouch
# for a sibling reviewer's skip.
CODEX_VERDICT=$(jq -r --arg mt "$MARKER_TIME" \
'[.[] | select(.user.login == "github-actions[bot]") | select(.body | contains("## Codex Review")) | select(.created_at < $mt)] | last | .body // ""' <<<"$COMMENTS" \
| grep -m1 -oE '(Good to merge|Mergeable, but should ideally address nits|Should address issues before merging)' || true)
fi
if [ -n "$MARKER_TIME" ] && [ -n "$CODEX_VERDICT" ] && [ "$CODEX_VERDICT" != "Should address issues before merging" ]; then
echo "Clean review round marker found for $HEAD_SHA with pre-marker non-blocking Codex verdict; skipping redundant review."
echo "skip=true" >> "$GITHUB_OUTPUT"
else
echo "skip=false" >> "$GITHUB_OUTPUT"
fi
- name: Checkout repository
if: steps.marker.outputs.skip != 'true'
uses: actions/checkout@v5
with:
fetch-depth: 1
- name: Check EE access
if: steps.marker.outputs.skip != 'true'
id: ee
env:
EE_TOKEN: ${{ secrets.WINDMILL_EE_PRIVATE_ACCESS }}
run: |
if [ -n "$EE_TOKEN" ]; then
echo "available=true" >> "$GITHUB_OUTPUT"
echo "ee_repo_ref=$(cat ./backend/ee-repo-ref.txt)" >> "$GITHUB_OUTPUT"
else
echo "available=false" >> "$GITHUB_OUTPUT"
fi
- name: Checkout EE repository
if: steps.ee.outputs.available == 'true'
uses: actions/checkout@v5
with:
repository: windmill-labs/windmill-ee-private
path: ./windmill-ee-private
ref: ${{ steps.ee.outputs.ee_repo_ref }}
token: ${{ secrets.WINDMILL_EE_PRIVATE_ACCESS }}
fetch-depth: 1
- name: Substitute EE code
if: steps.ee.outputs.available == 'true'
run: ./backend/substitute_ee_code.sh --copy --dir ./windmill-ee-private
- name: Resolve PR number
if: steps.marker.outputs.skip != 'true'
id: resolve
env:
GH_TOKEN: ${{ github.token }}
REPO: ${{ github.repository }}
INPUT_PR_NUMBER: ${{ inputs.pr_number }}
EVENT_PR_NUMBER: ${{ github.event.pull_request.number }}
EVENT_PR_AUTHOR: ${{ github.event.pull_request.user.login }}
run: |
if [ -n "$INPUT_PR_NUMBER" ]; then
PR_NUMBER="$INPUT_PR_NUMBER"
PR_AUTHOR=$(gh api "repos/$REPO/pulls/$PR_NUMBER" --jq '.user.login')
else
PR_NUMBER="$EVENT_PR_NUMBER"
PR_AUTHOR="$EVENT_PR_AUTHOR"
fi
echo "pr_number=$PR_NUMBER" >> "$GITHUB_OUTPUT"
echo "pr_author=$PR_AUTHOR" >> "$GITHUB_OUTPUT"
- name: Fetch prior PR discussion
if: steps.marker.outputs.skip != 'true'
id: prior
env:
GH_TOKEN: ${{ github.token }}
REPO: ${{ github.repository }}
PR_NUMBER: ${{ steps.resolve.outputs.pr_number }}
run: |
gh api "repos/$REPO/issues/$PR_NUMBER/comments?per_page=100" \
--jq '[.[] | {user: .user.login, created_at: .created_at, body: (.body | .[:4000])}] | sort_by(.created_at) | .[-20:]' \
> prior-comments.json || echo "[]" > prior-comments.json
jq -r '
if length == 0 then ""
else
"## Prior PR discussion (most recent up to 20 comments)\n\nIf you have already reviewed this PR (look for your own earlier comment), focus on what changed since then per the diff and respect any decisions the human made in replies. Do not re-flag findings the human already pushed back on.\n\n" +
(map("### @\(.user) (\(.created_at))\n\n\(.body)") | join("\n\n---\n\n"))
end
' prior-comments.json > prior-comments.md
- name: Read review prompt
if: steps.marker.outputs.skip != 'true'
id: review-prompt
env:
EXTRA_PROMPT: ${{ inputs.extra_prompt }}
run: |
# prior-comments.md is PR comment text verbatim, and commenting needs no write access.
# With a fixed delimiter, a comment containing a bare `EOF` line closes the block early:
# the step dies, and whatever follows in that comment is read as further environment
# assignments for the rest of this job, which holds the review tokens. Hence a random
# delimiter, per GitHub's guidance for untrusted multiline values.
delimiter="REVIEW_PROMPT_EOF_$(openssl rand -hex 16)"
{
echo "REVIEW_PROMPT<<$delimiter"
cat REVIEW.md
echo ''
cat .claude/review-prompt.md
if [ -n "$EXTRA_PROMPT" ]; then
echo ''
echo '## Additional reviewer instructions'
echo ''
printf '%s\n' "$EXTRA_PROMPT"
fi
if [ -s prior-comments.md ]; then
echo ''
cat prior-comments.md
fi
echo "$delimiter"
} >> "$GITHUB_ENV"
- name: Automatic PR Review
if: steps.marker.outputs.skip != 'true'
uses: anthropics/claude-code-action@v1
with:
claude_code_oauth_token: ${{ secrets.CLAUDE_CODE_OAUTH_TOKEN }}
track_progress: true
prompt: |
REPO: ${{ github.repository }}
PR NUMBER: ${{ steps.resolve.outputs.pr_number }}
PR AUTHOR: ${{ steps.resolve.outputs.pr_author }}
${{ env.REVIEW_PROMPT }}
claude_args: |
--allowedTools "mcp__github_inline_comment__create_inline_comment,Bash(gh pr comment:*),Bash(gh pr diff:*),Bash(gh pr view:*)"
--model claude-opus-5