Skip to content

Commit 2a00618

Browse files
Sync codex-review.yml to latest (deep-crawl prompt + hardening)
1 parent e8d1523 commit 2a00618

1 file changed

Lines changed: 75 additions & 34 deletions

File tree

.github/workflows/codex-review.yml

Lines changed: 75 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -2,19 +2,32 @@ name: Codex PR Review
22

33
on:
44
pull_request:
5+
branches: [main]
56
types: [opened, synchronize, reopened]
67
issue_comment:
78
types: [created]
89

10+
# Only collide for runs that review the same PR (a push or a trusted @codex review
11+
# on PR #N share group codex-review-N). The comment branch mirrors the full authorize
12+
# gate, including author_association, so an untrusted comment can never land in the
13+
# shared group and cancel an in-flight review; it falls back to the unique run id.
914
concurrency:
10-
group: codex-review-${{ github.event.pull_request.number || github.event.issue.number }}
15+
group: >-
16+
codex-review-${{
17+
(github.event_name == 'pull_request' && github.event.pull_request.number) ||
18+
(github.event_name == 'issue_comment' &&
19+
github.event.issue.pull_request != null &&
20+
contains(github.event.comment.body, '@codex review') &&
21+
contains(fromJSON('["OWNER","MEMBER","COLLABORATOR"]'), github.event.comment.author_association) &&
22+
github.event.issue.number) ||
23+
github.run_id }}
1124
cancel-in-progress: true
1225

1326
jobs:
1427
authorize:
15-
# Trigger gate: PR events always, comment events only for "@codex review" from a
16-
# trusted commenter. The step then restricts to same-repo (non-fork) PRs so a
17-
# public fork can never reach the privileged checkout below.
28+
# Trigger gate: PR events and "@codex review" from a trusted commenter. The
29+
# step then restricts to same-repo (non-fork) PRs so fork-controlled content
30+
# can never reach the secret-backed checkout/review below.
1831
if: >
1932
github.event_name == 'pull_request' ||
2033
(github.event.issue.pull_request != null &&
@@ -91,40 +104,56 @@ jobs:
91104
"required": ["summary", "findings"]
92105
}
93106
prompt: |
94-
You are reviewing pull request #${{ needs.authorize.outputs.pr }} in ${{ github.repository }}.
95-
96-
The PR's changes are exactly the diff between the merge commit's two parents.
97-
Run `git diff HEAD^1 HEAD^2` to see everything that changed, and
98-
`git diff HEAD^1 HEAD^2 -- <path>` to focus on a single file.
107+
You are a senior staff engineer doing a deep, context-aware review of pull
108+
request #${{ needs.authorize.outputs.pr }} in ${{ github.repository }}. Review
109+
like Devin: do not skim the diff, investigate the whole repository to judge
110+
each change in the context of the code around it. The full repo is checked
111+
out at the PR merge commit and you have a read-only shell, so actually explore.
99112
100-
Review ONLY those changes. Report high-signal findings only.
113+
Step 1 - establish what changed:
114+
- `git diff HEAD^1 HEAD^2 --stat` for the shape, then
115+
`git diff HEAD^1 HEAD^2 -- <path>` per file for the exact changes.
101116
102-
Correctness & safety:
103-
- logic errors, unhandled edge cases, broken assumptions
104-
- security vulnerabilities
105-
- data loss, concurrency hazards, resource leaks
117+
Step 2 - crawl the repository for context. Do not review a hunk in isolation:
118+
- Open each changed file IN FULL (not just the diff window) to understand
119+
surrounding logic, invariants, and intent.
120+
- Trace every symbol the change touches outward through the repo. For each
121+
changed function/class/constant/export/route/env var, find its definition
122+
and ALL usages with `git grep -n` / ripgrep, and read those call sites.
123+
- Follow imports both directions: what this code depends on, and what depends
124+
on it. Read the real implementations being called, never assume behavior.
125+
- Pull in the related files the diff did NOT touch but should be checked
126+
against: callers, tests, type definitions, schemas/migrations, configs,
127+
API contracts, fixtures, and docs.
106128
107-
Design & code quality:
108-
- the soundness of the overall approach, not just line-level bugs
109-
- elegance: is there a simpler, cleaner way to achieve the same result?
110-
- abstraction: prefer the most general clean abstraction that fits the problem,
111-
without over-engineering for cases that don't exist
112-
- redundancy: flag duplicated logic, dead code, and anything that violates DRY
129+
Step 3 - assess impact across the whole repo, not just the changed lines:
130+
- Ripple effects: does this change break or silently require updates in
131+
callers, tests, types, serialization, DB schema, or public contracts
132+
elsewhere in the repo? Did the PR update everything it needed to?
133+
- Consistency: does it match this repo's established conventions and patterns
134+
(naming, error handling, logging, layering, auth)? Cite the existing pattern.
135+
- Correctness & safety: logic errors, unhandled edge cases, broken
136+
assumptions, security holes, data loss, concurrency hazards, resource leaks.
137+
- Design: soundness of the overall approach; a simpler/cleaner way; the right
138+
abstraction without over-engineering; duplicated logic / dead code / DRY.
113139
114-
Skip pure formatting and style nits.
115-
116-
Report at most the 5 most important findings. Consolidate an issue that
117-
recurs in several places into one finding at the most representative location.
140+
Report high-signal findings only; skip pure formatting and style nits. Report
141+
at most the 5 most important findings, consolidating a recurring issue into a
142+
single finding at the most representative location. A finding may be rooted in
143+
an untouched file (e.g. a caller this PR breaks): anchor it to the changed line
144+
that causes the problem and name the affected file(s) in the comment.
118145
119146
Output JSON matching the provided schema:
120-
- `summary`: one or two sentences on the PR overall. If there are no real
121-
issues, set summary to "No issues found." and findings to [].
147+
- `summary`: one or two sentences on the PR overall, reflecting repo-wide
148+
impact. If there are no real issues, set summary to "No issues found." and
149+
findings to [].
122150
- `findings[].path`: repository-relative file path, exactly as git reports it.
123151
- `findings[].line`: the line number in the NEW (post-change) version of the
124152
file. It MUST be a line the PR adds or modifies.
125153
- `findings[].severity`: "blocking" or "consider".
126-
- `findings[].comment`: markdown review comment with a short code snippet and a
127-
concrete fix.
154+
- `findings[].comment`: markdown review comment that explains the repo-wide
155+
impact (reference the specific other files/call sites you inspected), with a
156+
short code snippet and a concrete fix.
128157
129158
post_review:
130159
needs: [authorize, review]
@@ -148,6 +177,14 @@ jobs:
148177
const INLINE_MARKER = '<!-- codex-review-inline -->';
149178
const MAX_COMMENTS = 5;
150179
180+
// Only ever mutate comments this workflow itself authored, identified by
181+
// the GITHUB_TOKEN bot actor plus our hidden marker. Never touch a human's
182+
// (or another bot's) comment even if it happens to quote a marker.
183+
const isOurComment = (c, marker) =>
184+
c.user?.type === 'Bot' &&
185+
c.user?.login === 'github-actions[bot]' &&
186+
c.body?.includes(marker);
187+
151188
// Parse Codex JSON. With --output-schema the result is already pure JSON,
152189
// and its findings may contain fenced code blocks, so never grab an inner
153190
// fence: parse the whole string first, then a fence wrapping the whole
@@ -208,14 +245,16 @@ jobs:
208245
}
209246
}
210247
211-
// Always clear our prior inline comments first, so findings resolved in a
212-
// later push disappear even when this run produces no inline comments.
248+
// Clear our own prior inline comments so resolved findings disappear on a
249+
// later push, but never delete a thread that has replies (someone engaged
250+
// with it) or a comment we did not author.
213251
try {
214252
const prior = await github.paginate(github.rest.pulls.listReviewComments, {
215253
owner, repo, pull_number, per_page: 100,
216254
});
255+
const repliedTo = new Set(prior.filter(c => c.in_reply_to_id).map(c => c.in_reply_to_id));
217256
for (const c of prior) {
218-
if (c.body && c.body.includes(INLINE_MARKER)) {
257+
if (isOurComment(c, INLINE_MARKER) && !c.in_reply_to_id && !repliedTo.has(c.id)) {
219258
try { await github.rest.pulls.deleteReviewComment({ owner, repo, comment_id: c.id }); }
220259
catch {}
221260
}
@@ -228,7 +267,9 @@ jobs:
228267
try {
229268
await github.rest.pulls.createReview({
230269
owner, repo, pull_number, commit_id: headSha,
231-
event: 'COMMENT', comments: inline,
270+
event: 'COMMENT',
271+
body: `${SUMMARY_MARKER.replace('summary','review-header')}\nCodex flagged ${inline.length} item(s) inline below.`,
272+
comments: inline,
232273
});
233274
inlinePosted = true;
234275
} catch (err) {
@@ -247,11 +288,11 @@ jobs:
247288
for (const o of leftover) body += `\n- \`${o.path}:${o.line}\` **${o.sev}** ${o.comment}`;
248289
}
249290
250-
// Upsert one rolling summary comment instead of stacking on each push.
291+
// Upsert our own rolling summary comment (never overwrite a human's).
251292
const comments = await github.paginate(github.rest.issues.listComments, {
252293
owner, repo, issue_number: pull_number, per_page: 100,
253294
});
254-
const existing = comments.find(c => c.body && c.body.includes(SUMMARY_MARKER));
295+
const existing = comments.find(c => isOurComment(c, SUMMARY_MARKER));
255296
if (existing) {
256297
await github.rest.issues.updateComment({ owner, repo, comment_id: existing.id, body });
257298
} else {

0 commit comments

Comments
 (0)