Skip to content

Commit d3be055

Browse files
PETE - Recover from dropped pull_request events (#13667)
## Summary - Listen for `synchronize` and `ready_for_review` on top of `opened` in `.github/workflows/pr-test-checker.yml` so PETE has a recovery path when GitHub drops a `pull_request: opened` delivery. - Add an idempotency guard to `.github/actions/pr-test-checker/action.yml`: any `pull_request`-triggered run short-circuits when a PETE comment already exists on the PR. `synchronize` / `ready_for_review` therefore only do work when the original `opened` was missed -- they don't re-grade on every push. - `/recheck-tests` (the `issue_comment` flow) sets `force-regrade=true` and bypasses the guard, so a manual re-grade is never short-circuited. - Rename the `pull_request` job from `Grade on open` to `Grade on PR change` to reflect the broadened trigger set. ## Motivation [#13664](#13664) (opened by @samclark2015) didn't get a PETE verdict because GitHub silently dropped the `pull_request: opened` event delivery. The `pull_request_target` channel (CLA) fired fine, but no `pull_request` workflows fired on open. PETE only listened for `opened`, so it had no recovery path; other workflows like *PR: Comment* recovered because they also listen for `synchronize`. ## Observable behavior - New PR opened (event delivered): PETE grades on `opened`. Done. - New PR opened (event dropped): no comment exists, so the first `synchronize` (next push) or `ready_for_review` (draft -> ready) fires PETE, which posts the verdict. - Subsequent pushes once a verdict exists: action looks up the existing PETE comment, exits early, no LLM tokens spent. - `/recheck-tests` comment: always re-grades regardless of any existing comment.
1 parent 8a7680c commit d3be055

2 files changed

Lines changed: 61 additions & 5 deletions

File tree

.github/actions/pr-test-checker/action.yml

Lines changed: 45 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,10 @@ inputs:
1010
repo-root:
1111
description: "Absolute path to the PR-head checkout the agent reads source from. The action itself runs from a trusted base-branch checkout; this input lets the workflow point the agent at a separate, untrusted checkout for source-reading only. Must be set for any flow that handles secrets."
1212
required: true
13+
force-regrade:
14+
description: "When 'true', always run the analyzer even if a PETE comment already exists. Set by the /recheck-tests flow so a manual re-grade is never short-circuited. Default 'false' so `pull_request` events (opened / synchronize / ready_for_review) skip when a verdict has already been posted -- this lets `synchronize` recover a missed `opened` delivery without re-grading every subsequent push."
15+
required: false
16+
default: "false"
1317
model:
1418
description: "Anthropic model identifier"
1519
required: false
@@ -21,12 +25,42 @@ inputs:
2125
runs:
2226
using: composite
2327
steps:
28+
- name: Check for prior grade (idempotency guard)
29+
# When called from a `pull_request` event, skip if a PETE comment
30+
# already exists on the PR. This lets `synchronize` / `ready_for_review`
31+
# act purely as recovery channels for a dropped `opened` delivery
32+
# (see #13664) without re-grading on every subsequent push. The
33+
# /recheck-tests flow sets force-regrade=true to bypass this.
34+
id: idempotency
35+
if: inputs.force-regrade != 'true'
36+
shell: bash
37+
env:
38+
GH_TOKEN: ${{ github.token }}
39+
GITHUB_REPOSITORY: ${{ github.repository }}
40+
PR_NUMBER: ${{ inputs.pr-number }}
41+
run: |
42+
MARKER="<!-- pr-test-checker -->"
43+
# Match on BOTH the marker AND the bot author -- a non-bot commenter
44+
# could otherwise paste the marker to suppress future auto-grading
45+
# until someone manually runs /recheck-tests.
46+
EXISTING_ID=$(gh api "repos/${GITHUB_REPOSITORY}/issues/${PR_NUMBER}/comments" --paginate \
47+
--jq "[.[] | select(.body | contains(\"$MARKER\")) | select(.user.login == \"github-actions[bot]\")][0].id // empty")
48+
if [ -n "$EXISTING_ID" ]; then
49+
echo "PETE comment already exists (id=${EXISTING_ID}); skipping. Use /recheck-tests to force a re-grade."
50+
echo "skip=true" >> "$GITHUB_OUTPUT"
51+
else
52+
echo "No PETE comment yet; proceeding."
53+
echo "skip=false" >> "$GITHUB_OUTPUT"
54+
fi
55+
2456
- name: Set up Node
57+
if: steps.idempotency.outputs.skip != 'true'
2558
uses: actions/setup-node@v4
2659
with:
2760
node-version: "20"
2861

2962
- name: Install action dependencies
63+
if: steps.idempotency.outputs.skip != 'true'
3064
shell: bash
3165
working-directory: ${{ github.action_path }}
3266
run: npm ci --no-audit --no-fund
@@ -36,6 +70,7 @@ runs:
3670
# the musl variant over glibc on Linux runners. Installing globally and
3771
# passing the resolved path via pathToClaudeCodeExecutable sidesteps it.
3872
# See claude-agent-sdk-typescript#296.
73+
if: steps.idempotency.outputs.skip != 'true'
3974
shell: bash
4075
run: |
4176
npm install -g @anthropic-ai/claude-code
@@ -44,6 +79,7 @@ runs:
4479
4580
- name: Prepare working directory
4681
id: prep
82+
if: steps.idempotency.outputs.skip != 'true'
4783
shell: bash
4884
run: |
4985
WORK_DIR="${RUNNER_TEMP}/pr-test-checker"
@@ -55,6 +91,7 @@ runs:
5591
# Runs the gather script from the action's own directory (i.e. the
5692
# trusted base-branch checkout), not from the PR head. PR-head code
5793
# must never execute with secrets in scope.
94+
if: steps.idempotency.outputs.skip != 'true'
5895
shell: bash
5996
env:
6097
GH_TOKEN: ${{ github.token }}
@@ -72,6 +109,7 @@ runs:
72109
# so they are read from the trusted base-branch checkout. Only
73110
# REPO_ROOT (where the agent's Read/Grep/Glob tools operate) points
74111
# at the untrusted PR-head checkout.
112+
if: steps.idempotency.outputs.skip != 'true'
75113
shell: bash
76114
working-directory: ${{ github.action_path }}
77115
env:
@@ -84,6 +122,7 @@ runs:
84122
run: node analyze.mjs
85123

86124
- name: Upsert PR comment
125+
if: steps.idempotency.outputs.skip != 'true'
87126
shell: bash
88127
env:
89128
GH_TOKEN: ${{ github.token }}
@@ -98,10 +137,12 @@ runs:
98137
fi
99138
100139
MARKER="<!-- pr-test-checker -->"
101-
# Find an existing comment with our marker. Pull only what we need from
102-
# the comments listing (id + body) to keep the response small.
140+
# Find an existing comment with our marker AND authored by the bot.
141+
# Filtering on author prevents a spoofed marker in a human comment
142+
# from being picked up here (either as a PATCH target that 403s, or
143+
# by hiding the bot's own comment from the upsert lookup).
103144
EXISTING_ID=$(gh api "repos/${GITHUB_REPOSITORY}/issues/${PR_NUMBER}/comments" --paginate \
104-
--jq "[.[] | select(.body | contains(\"$MARKER\"))][0].id // empty")
145+
--jq "[.[] | select(.body | contains(\"$MARKER\")) | select(.user.login == \"github-actions[bot]\")][0].id // empty")
105146
106147
if [ -n "$EXISTING_ID" ]; then
107148
echo "Updating existing comment $EXISTING_ID"
@@ -115,7 +156,7 @@ runs:
115156
fi
116157
117158
- name: Upload analyzer artifacts
118-
if: always()
159+
if: always() && steps.idempotency.outputs.skip != 'true'
119160
uses: actions/upload-artifact@v4
120161
with:
121162
name: pr-test-checker-${{ inputs.pr-number }}

.github/workflows/pr-test-checker.yml

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,18 @@ name: "PR Test Checker"
44
# Pilot scope: only runs for PR authors / commenters in the allowlist below.
55
# To widen the pilot, edit both ALLOWLIST refs in this file (kept in sync).
66
#
7+
# Trigger recovery: we listen for `opened`, `synchronize`, AND `ready_for_review`
8+
# on `pull_request`, not just `opened`. GitHub occasionally drops `opened`
9+
# deliveries (see #13664, where the `pull_request_target` CLA event fired but
10+
# `pull_request: opened` didn't, leaving PETE with no verdict), so the extra
11+
# types give us a recovery path on the next push or draft->ready transition.
12+
# Observable behavior: PETE grades on PR creation, and only re-grades on
13+
# explicit `/recheck-tests`. To avoid re-grading on every push, the action
14+
# short-circuits any `pull_request`-triggered run when a PETE comment already
15+
# exists on the PR -- `synchronize` and `ready_for_review` therefore only do
16+
# work in the rare case where the initial `opened` was missed. `/recheck-tests`
17+
# (an `issue_comment` event) sets `force-regrade=true` and bypasses the guard.
18+
#
719
# Security model: each job checks out two trees -- the BASE branch (trusted
820
# action + skill code, used to run the analyzer with secrets) and the PR HEAD
921
# (untrusted source code, mounted as read-only data for the agent's
@@ -18,13 +30,15 @@ on:
1830
pull_request:
1931
types:
2032
- opened
33+
- synchronize
34+
- ready_for_review
2135
issue_comment:
2236
types:
2337
- created
2438

2539
jobs:
2640
on-open:
27-
name: Grade on open
41+
name: Grade on PR change
2842
if: |
2943
github.event_name == 'pull_request' &&
3044
contains(fromJson('["jonvanausdeln", "sharon-wang", "melissa-barca", "samclark2015", "timtmok"]'), github.event.pull_request.user.login)
@@ -143,3 +157,4 @@ jobs:
143157
pr-number: ${{ github.event.issue.number }}
144158
anthropic-api-key: ${{ env.ANTHROPIC_KEY }}
145159
repo-root: ${{ github.workspace }}/pr-head
160+
force-regrade: "true"

0 commit comments

Comments
 (0)