Skip to content

Commit 4f28e4b

Browse files
authored
Trigger CI checks on manual review (#71)
Signed-off-by: Derek Misler <derek.misler@docker.com>
1 parent daaed6f commit 4f28e4b

File tree

6 files changed

+174
-12
lines changed

6 files changed

+174
-12
lines changed

.github/workflows/pr-describe.yml

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,10 +18,33 @@ jobs:
1818
contents: read
1919
pull-requests: write
2020
issues: write
21+
checks: write
2122
steps:
2223
- name: Check out Git repository
2324
uses: actions/checkout@d632683dd7b4114ad314bca15554477dd762a938 # v4.2.0
2425

26+
- name: Create check run
27+
id: create-check
28+
continue-on-error: true # Don't fail if checks: write permission is missing
29+
uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8
30+
with:
31+
script: |
32+
const prNumber = context.issue.number;
33+
const { data: pr } = await github.rest.pulls.get({
34+
owner: context.repo.owner,
35+
repo: context.repo.repo,
36+
pull_number: prNumber
37+
});
38+
const { data: check } = await github.rest.checks.create({
39+
owner: context.repo.owner,
40+
repo: context.repo.repo,
41+
name: 'Generate PR Description',
42+
head_sha: pr.head.sha,
43+
status: 'in_progress',
44+
started_at: new Date().toISOString()
45+
});
46+
core.setOutput('check-id', check.id);
47+
2548
# Generate GitHub App token so actions appear as the custom app (optional - falls back to github.token)
2649
- name: Get GitHub App token
2750
id: app-token
@@ -284,3 +307,25 @@ jobs:
284307
set -euo pipefail
285308
rm -f commits.txt files.txt pr.diff || true
286309
echo "🧹 Cleaned up temporary files"
310+
311+
- name: Update check run
312+
if: always() && steps.create-check.outputs.check-id != ''
313+
uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8
314+
env:
315+
CHECK_ID: ${{ steps.create-check.outputs.check-id }}
316+
JOB_STATUS: ${{ job.status }}
317+
with:
318+
script: |
319+
const conclusion = process.env.JOB_STATUS === 'cancelled' ? 'cancelled' : process.env.JOB_STATUS === 'success' ? 'success' : 'failure';
320+
try {
321+
await github.rest.checks.update({
322+
owner: context.repo.owner,
323+
repo: context.repo.repo,
324+
check_run_id: parseInt(process.env.CHECK_ID, 10),
325+
status: 'completed',
326+
conclusion: conclusion,
327+
completed_at: new Date().toISOString()
328+
});
329+
} catch (error) {
330+
core.warning(`Failed to update check run: ${error.message}`);
331+
}

.github/workflows/review-pr.yml

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818
# contents: read # Read repository files and PR diffs
1919
# pull-requests: write # Post review comments and approve/request changes
2020
# issues: write # Create security incident issues if secrets are detected in output
21+
# checks: write # (Optional) Show review progress as a check run on the PR
2122
# secrets:
2223
# ANTHROPIC_API_KEY: ${{ secrets.ANTHROPIC_API_KEY }}
2324
# CAGENT_ORG_MEMBERSHIP_TOKEN: ${{ secrets.CAGENT_ORG_MEMBERSHIP_TOKEN }} # PAT with read:org scope; gates auto-reviews to org members only
@@ -99,6 +100,7 @@ permissions:
99100
contents: read
100101
pull-requests: write
101102
issues: write
103+
checks: write
102104

103105
jobs:
104106
# ==========================================================================
@@ -203,6 +205,30 @@ jobs:
203205
exit-code: ${{ steps.run-review.outputs.exit-code }}
204206

205207
steps:
208+
- name: Create check run
209+
id: create-check
210+
continue-on-error: true # Don't fail if caller didn't grant checks: write
211+
uses: actions/github-script@60a0d83039c74a4aee543508d2ffcb1c3799cdea # v7.0.1
212+
env:
213+
PR_NUMBER: ${{ inputs.pr-number || github.event.issue.number }}
214+
with:
215+
script: |
216+
const prNumber = parseInt(process.env.PR_NUMBER, 10);
217+
const { data: pr } = await github.rest.pulls.get({
218+
owner: context.repo.owner,
219+
repo: context.repo.repo,
220+
pull_number: prNumber
221+
});
222+
const { data: check } = await github.rest.checks.create({
223+
owner: context.repo.owner,
224+
repo: context.repo.repo,
225+
name: 'PR Review',
226+
head_sha: pr.head.sha,
227+
status: 'in_progress',
228+
started_at: new Date().toISOString()
229+
});
230+
core.setOutput('check-id', check.id);
231+
206232
# Checkout PR head (not default branch)
207233
# Note: Authorization is handled by the composite action's built-in check
208234
- name: Checkout PR head
@@ -240,6 +266,28 @@ jobs:
240266
nebius-api-key: ${{ secrets.NEBIUS_API_KEY }}
241267
mistral-api-key: ${{ secrets.MISTRAL_API_KEY }}
242268

269+
- name: Update check run
270+
if: always() && steps.create-check.outputs.check-id != ''
271+
uses: actions/github-script@60a0d83039c74a4aee543508d2ffcb1c3799cdea # v7.0.1
272+
env:
273+
CHECK_ID: ${{ steps.create-check.outputs.check-id }}
274+
JOB_STATUS: ${{ job.status }}
275+
with:
276+
script: |
277+
const conclusion = process.env.JOB_STATUS === 'cancelled' ? 'cancelled' : process.env.JOB_STATUS === 'success' ? 'success' : 'failure';
278+
try {
279+
await github.rest.checks.update({
280+
owner: context.repo.owner,
281+
repo: context.repo.repo,
282+
check_run_id: parseInt(process.env.CHECK_ID, 10),
283+
status: 'completed',
284+
conclusion: conclusion,
285+
completed_at: new Date().toISOString()
286+
});
287+
} catch (error) {
288+
core.warning(`Failed to update check run: ${error.message}`);
289+
}
290+
243291
# ==========================================================================
244292
# CAPTURE FEEDBACK
245293
# Saves feedback data as an artifact for lazy processing. This job

.github/workflows/self-review-pr.yml

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ permissions:
1717
contents: read
1818
pull-requests: write
1919
issues: write
20+
checks: write
2021

2122
jobs:
2223
# ==========================================================================
@@ -111,6 +112,28 @@ jobs:
111112
HAS_APP_SECRETS: ${{ secrets.CAGENT_REVIEWER_APP_ID != '' }}
112113

113114
steps:
115+
- name: Create check run
116+
id: create-check
117+
continue-on-error: true # Don't fail if checks: write permission is missing
118+
uses: actions/github-script@60a0d83039c74a4aee543508d2ffcb1c3799cdea # v7.0.1
119+
with:
120+
script: |
121+
const prNumber = context.issue.number;
122+
const { data: pr } = await github.rest.pulls.get({
123+
owner: context.repo.owner,
124+
repo: context.repo.repo,
125+
pull_number: prNumber
126+
});
127+
const { data: check } = await github.rest.checks.create({
128+
owner: context.repo.owner,
129+
repo: context.repo.repo,
130+
name: 'PR Review',
131+
head_sha: pr.head.sha,
132+
status: 'in_progress',
133+
started_at: new Date().toISOString()
134+
});
135+
core.setOutput('check-id', check.id);
136+
114137
# Checkout PR head (not default branch)
115138
# Note: Authorization is handled by the composite action's built-in check
116139
- name: Checkout PR head
@@ -145,6 +168,28 @@ jobs:
145168
nebius-api-key: ${{ secrets.NEBIUS_API_KEY }}
146169
mistral-api-key: ${{ secrets.MISTRAL_API_KEY }}
147170

171+
- name: Update check run
172+
if: always() && steps.create-check.outputs.check-id != ''
173+
uses: actions/github-script@60a0d83039c74a4aee543508d2ffcb1c3799cdea # v7.0.1
174+
env:
175+
CHECK_ID: ${{ steps.create-check.outputs.check-id }}
176+
JOB_STATUS: ${{ job.status }}
177+
with:
178+
script: |
179+
const conclusion = process.env.JOB_STATUS === 'cancelled' ? 'cancelled' : process.env.JOB_STATUS === 'success' ? 'success' : 'failure';
180+
try {
181+
await github.rest.checks.update({
182+
owner: context.repo.owner,
183+
repo: context.repo.repo,
184+
check_run_id: parseInt(process.env.CHECK_ID, 10),
185+
status: 'completed',
186+
conclusion: conclusion,
187+
completed_at: new Date().toISOString()
188+
});
189+
} catch (error) {
190+
core.warning(`Failed to update check run: ${error.message}`);
191+
}
192+
148193
# ==========================================================================
149194
# CAPTURE FEEDBACK
150195
# Saves feedback data as an artifact for lazy processing. This job

README.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -69,6 +69,7 @@ jobs:
6969
contents: read # Read repository files and PR diffs
7070
pull-requests: write # Post review comments and approve/request changes
7171
issues: write # Create security incident issues if secrets are detected in output
72+
checks: write # (Optional) Show review progress as a check run on the PR
7273
secrets:
7374
ANTHROPIC_API_KEY: ${{ secrets.ANTHROPIC_API_KEY }}
7475
CAGENT_ORG_MEMBERSHIP_TOKEN: ${{ secrets.CAGENT_ORG_MEMBERSHIP_TOKEN }} # PAT with read:org scope; gates auto-reviews to org members only
@@ -217,6 +218,7 @@ permissions:
217218
contents: read
218219
pull-requests: write
219220
issues: write
221+
checks: write # Optional: show review progress as a check run on PRs
220222
```
221223

222224

review-pr/README.md

Lines changed: 16 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@ jobs:
2929
contents: read # Read repository files and PR diffs
3030
pull-requests: write # Post review comments and approve/request changes
3131
issues: write # Create security incident issues if secrets are detected in output
32+
checks: write # (Optional) Show review progress as a check run on the PR
3233
secrets:
3334
ANTHROPIC_API_KEY: ${{ secrets.ANTHROPIC_API_KEY }}
3435
CAGENT_ORG_MEMBERSHIP_TOKEN: ${{ secrets.CAGENT_ORG_MEMBERSHIP_TOKEN }} # PAT with read:org scope; gates auto-reviews to org members only
@@ -53,7 +54,7 @@ The workflow automatically handles:
5354
| Trigger | Behavior |
5455
| ----------------------- | --------------------------------------------------------------------------------------- |
5556
| PR opened/ready | Auto-reviews PRs from your org members (if `CAGENT_ORG_MEMBERSHIP_TOKEN` is configured) |
56-
| `/review` comment | Manual review on any PR |
57+
| `/review` comment | Manual review on any PR (shows as a check run on the PR if `checks: write` is granted) |
5758
| Reply to review comment | Learns from feedback to improve future reviews |
5859

5960
---
@@ -131,6 +132,7 @@ on:
131132
permissions:
132133
contents: read
133134
pull-requests: write
135+
checks: write # Optional: show review progress as a check run
134136
135137
jobs:
136138
review:
@@ -318,9 +320,15 @@ When no issues are found:
318320

319321
---
320322

321-
## Reactions
323+
## Progress Indicators
322324

323-
The action uses emoji reactions on your `/review` comment to indicate progress:
325+
### Check Runs
326+
327+
When the workflow has `checks: write` permission, reviews appear as check runs on the PR's Checks tab. This makes it easy to see review status at a glance — `in_progress` while reviewing, then `success`, `failure`, or `cancelled` when done. If `checks: write` is not granted, the workflow still works normally without check runs.
328+
329+
### Reactions
330+
331+
The action also uses emoji reactions on your `/review` comment to indicate progress:
324332

325333
| Stage | Reaction | Meaning |
326334
| ----------------- | -------- | ------------------------------ |
@@ -440,6 +448,11 @@ Each eval file in `review-pr/agents/evals/` contains:
440448
- Ensure the workflow has `pull-requests: write` permission
441449
- Check if the `github-token` has access to react to comments
442450

451+
**No check run showing on the PR?**
452+
453+
- Add `checks: write` to your workflow permissions (it's optional — the review works without it)
454+
- Check runs are created for manual (`/review`) triggers. Auto-reviews from `pull_request_target` already appear as workflow runs natively
455+
443456
**Learning doesn't seem to work?**
444457

445458
- You must **reply directly** to an agent comment (use the reply button, not a new comment)

review-pr/action.yml

Lines changed: 18 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -329,20 +329,29 @@ runs:
329329
THREAD_PATH=$(echo "$BOT_THREADS" | jq -r ".[$i].path")
330330
THREAD_LINE=$(echo "$BOT_THREADS" | jq -r ".[$i].line")
331331
332-
# Skip threads with null path or line (outdated diff positions, file-level comments)
333-
if [ "$THREAD_PATH" = "null" ] || [ -z "$THREAD_PATH" ] || \
334-
[ "$THREAD_LINE" = "null" ] || [ -z "$THREAD_LINE" ]; then
335-
echo " ⏭️ Keeping open: thread $THREAD_ID (path or line is null/outdated)"
332+
# Skip threads with null path (file-level or unknown context)
333+
if [ "$THREAD_PATH" = "null" ] || [ -z "$THREAD_PATH" ]; then
334+
echo " ⏭️ Keeping open: thread $THREAD_ID (no file path)"
336335
KEPT=$((KEPT + 1))
337336
continue
338337
fi
339338
340-
FILE_LINE="${THREAD_PATH}:${THREAD_LINE}"
341-
342-
if grep -qFx "$FILE_LINE" "$DIFF_LINES_FILE"; then
343-
echo " ⏭️ Keeping open: $FILE_LINE (still in diff)"
339+
# Null line means GitHub marked the comment "outdated" — the code changed
340+
# since the comment was posted and the position is no longer valid.
341+
# These are stale by definition and should be resolved.
342+
SHOULD_RESOLVE=false
343+
if [ "$THREAD_LINE" = "null" ] || [ -z "$THREAD_LINE" ]; then
344+
echo " 🔄 Outdated: $THREAD_PATH (position invalidated by push)"
345+
SHOULD_RESOLVE=true
346+
elif grep -qFx "${THREAD_PATH}:${THREAD_LINE}" "$DIFF_LINES_FILE"; then
347+
echo " ⏭️ Keeping open: ${THREAD_PATH}:${THREAD_LINE} (still in diff)"
344348
KEPT=$((KEPT + 1))
345349
else
350+
echo " 🔄 Stale: ${THREAD_PATH}:${THREAD_LINE} (no longer in diff)"
351+
SHOULD_RESOLVE=true
352+
fi
353+
354+
if [ "$SHOULD_RESOLVE" = "true" ]; then
346355
MUTATION_RESULT=$(gh api graphql -f query='
347356
mutation($threadId: ID!) {
348357
resolveReviewThread(input: { threadId: $threadId }) {
@@ -359,7 +368,7 @@ runs:
359368
continue
360369
fi
361370
362-
echo " ✅ Resolved: $FILE_LINE (no longer in diff)"
371+
echo " ✅ Resolved thread $THREAD_ID"
363372
RESOLVED=$((RESOLVED + 1))
364373
fi
365374
done

0 commit comments

Comments
 (0)