Skip to content

Commit 002879d

Browse files
authored
Merge pull request #417 from igerber/fix-audit-checkout-head
Use refs/pull/N/head for AI review checkout (unblock /ai-review on merged PRs)
2 parents c86fc2e + bab8fe4 commit 002879d

1 file changed

Lines changed: 41 additions & 6 deletions

File tree

.github/workflows/ai_pr_review.yml

Lines changed: 41 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -70,24 +70,52 @@ jobs:
7070
const { owner, repo } = context.repo;
7171
const pr = await github.rest.pulls.get({ owner, repo, pull_number: prNumber });
7272
73+
// head.repo can be null on fork PRs from deleted forks; fall back
74+
// to the base repo so checkout still has a sensible target.
75+
const headRepoFullName =
76+
pr.data.head.repo?.full_name || `${owner}/${repo}`;
77+
7378
core.setOutput("number", prNumber);
7479
core.setOutput("title", pr.data.title || "");
7580
core.setOutput("body", pr.data.body || "");
7681
core.setOutput("base_sha", pr.data.base.sha);
7782
core.setOutput("head_sha", pr.data.head.sha);
7883
core.setOutput("base_ref", pr.data.base.ref);
7984
core.setOutput("head_ref", pr.data.head.ref);
85+
core.setOutput("head_repo_full_name", headRepoFullName);
86+
core.setOutput("state", pr.data.state);
87+
88+
# Closed/merged PR (e.g. `/ai-review` rerun on a merged PR):
89+
# use the base-repo mirror of the PR head, which GitHub keeps
90+
# durably even after the fork is deleted or branches removed.
91+
# The previous workflow used `refs/pull/<N>/merge`, which is
92+
# garbage-collected on closed PRs — this path replaces that.
93+
- uses: actions/checkout@v6
94+
if: steps.pr.outputs.state != 'open'
95+
with:
96+
ref: refs/pull/${{ steps.pr.outputs.number }}/head
8097

98+
# Open PR: check out by head_sha from the head repo (= base repo
99+
# for owner PRs, = the fork for fork PRs). This avoids the
100+
# documented race where `refs/pull/<N>/head` on the base repo
101+
# has not yet mirrored a freshly API-created PR's head
102+
# (see .claude/commands/submit-pr.md:327-345). head_sha is
103+
# guaranteed to exist on the head repo for an open PR.
81104
- uses: actions/checkout@v6
105+
if: steps.pr.outputs.state == 'open'
82106
with:
83-
ref: refs/pull/${{ steps.pr.outputs.number }}/merge
107+
repository: ${{ steps.pr.outputs.head_repo_full_name }}
108+
ref: ${{ steps.pr.outputs.head_sha }}
84109

85-
- name: Pre-fetch base and head refs
110+
- name: Pre-fetch base SHA
86111
run: |
87112
set -euo pipefail
88-
git fetch --no-tags origin \
89-
"${{ steps.pr.outputs.base_ref }}" \
90-
+refs/pull/${{ steps.pr.outputs.number }}/head
113+
# base_sha lives on the base repo (github.repository), which differs
114+
# from origin when this is an open fork PR. Add an explicit `base`
115+
# remote so `git diff BASE_SHA HEAD_SHA` finds the base-side tree
116+
# regardless of which checkout path ran.
117+
git remote add base "https://github.com/${{ github.repository }}.git"
118+
git fetch --no-tags --depth=1 base "${{ steps.pr.outputs.base_sha }}"
91119
92120
- name: Fetch previous AI review (if any)
93121
id: prev_review
@@ -125,7 +153,14 @@ jobs:
125153
set -euo pipefail
126154
PROMPT=.github/codex/prompts/pr_review_compiled.md
127155
128-
cat .github/codex/prompts/pr_review.md > "$PROMPT"
156+
# Source the review prompt from base_sha rather than the PR head.
157+
# The prompt defines HOW the reviewer reviews; sourcing it from
158+
# base prevents a PR from modifying its own review rules. (Note:
159+
# docs/methodology/REGISTRY.md and TODO.md remain from the PR
160+
# head intentionally - the prompt instructs the reviewer to
161+
# recognize PR-added Note/Deviation labels and tracked TODOs as
162+
# mitigations, so those must reflect the PR's edits.)
163+
git show "${BASE_SHA}":.github/codex/prompts/pr_review.md > "$PROMPT"
129164
130165
# Sanitize untrusted text so hostile content can't close the
131166
# wrapper tags and inject instructions to the reviewer.

0 commit comments

Comments
 (0)