mirror of
https://github.com/windmill-labs/windmill.git
synced 2026-10-15 08:02:53 +00:00
* 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>
212 lines
9.3 KiB
YAML
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
|