Skip to content

Commit 87bdf2f

Browse files
authored
fix(ci): make Docs AI Review workflow work for fork PRs (#3577)
## Summary Fix two related issues in `.github/workflows/docs-ai-review.yml` so that the Docs AI Review packet + advisory comment works for fork-originated PRs (which is the common case in this repo). - **Rerun on push when labeled.** The `pull_request` trigger previously only listed the `labeled` action, so the workflow ran exactly once when the `ai-review-docs` label was first applied and did not re-run when the PR was updated with new commits. Add `synchronize` and `reopened` to the types list, and gate those two actions on the `ai-review-docs` label already being present on the PR. - **Split the advisory comment into a `workflow_run` workflow.** Pull requests from forks run `pull_request` workflows with a read-only `GITHUB_TOKEN` regardless of declared `permissions:`, so the inline comment step could never post on fork PRs and returned 403 on both the GraphQL `addComment` mutation (via `gh pr comment`) and the REST `issues/{n}/comments` endpoint. Following the standard GitHub pattern: - `docs-ai-review.yml` (pull_request trigger) now only prepares the packet and uploads it, together with a `pr-metadata.json` describing PR number / head SHA / base SHA / trigger name. - A new `docs-ai-review-comment.yml` (`workflow_run` trigger) downloads the artifact from the completed run, parses the metadata, and upserts the advisory comment via REST. Because `workflow_run` runs in the base-repo context, its `GITHUB_TOKEN` honors `issues: write` even for fork PRs. The comment workflow does not check out or execute any code from the fork — it only consumes the prebuilt packet and metadata JSON — so it avoids the footgun usually associated with giving write permissions to fork-triggered runs. ## Test plan - [ ] After merge, open a fresh fork PR and add the `ai-review-docs` label — confirm `Docs AI Review` runs (prepare packet + upload artifact). - [ ] Confirm `Docs AI Review Comment` fires via `workflow_run` afterwards and upserts a comment with the packet metadata. - [ ] Push a follow-up commit to the same PR — confirm `Docs AI Review` reruns on `synchronize` because the label is still applied, and the advisory comment is updated in place (PATCH path). - [ ] Post `/review-docs` as a COLLABORATOR/MEMBER on a PR — confirm `Docs AI Review` runs via `issue_comment` and the comment workflow follows. - [ ] Remove the label, push again — confirm `Docs AI Review` no longer runs.
1 parent 4c29798 commit 87bdf2f

2 files changed

Lines changed: 116 additions & 60 deletions

File tree

Lines changed: 97 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,97 @@
1+
name: Docs AI Review Comment
2+
3+
on:
4+
workflow_run:
5+
workflows: ["Docs AI Review"]
6+
types: [completed]
7+
8+
permissions:
9+
contents: read
10+
actions: read
11+
issues: write
12+
13+
jobs:
14+
upsert-advisory-comment:
15+
runs-on: ubuntu-latest
16+
timeout-minutes: 10
17+
if: github.event.workflow_run.conclusion == 'success'
18+
steps:
19+
- name: Locate review packet artifact
20+
id: locate
21+
env:
22+
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
23+
RUN_ID: ${{ github.event.workflow_run.id }}
24+
run: |
25+
artifacts_json=$(gh api \
26+
"repos/${{ github.repository }}/actions/runs/${RUN_ID}/artifacts" \
27+
--paginate)
28+
artifact_name=$(echo "$artifacts_json" \
29+
| jq -r '.artifacts[] | select(.name | startswith("docs-ai-review-packet-")) | .name' \
30+
| head -n 1)
31+
if [ -z "$artifact_name" ]; then
32+
echo "No docs-ai-review-packet artifact in run ${RUN_ID}; nothing to comment."
33+
echo "found=false" >> "$GITHUB_OUTPUT"
34+
else
35+
echo "found=true" >> "$GITHUB_OUTPUT"
36+
echo "name=$artifact_name" >> "$GITHUB_OUTPUT"
37+
fi
38+
39+
- name: Download review packet artifact
40+
if: steps.locate.outputs.found == 'true'
41+
uses: actions/download-artifact@v4
42+
with:
43+
name: ${{ steps.locate.outputs.name }}
44+
path: artifact
45+
run-id: ${{ github.event.workflow_run.id }}
46+
github-token: ${{ secrets.GITHUB_TOKEN }}
47+
48+
- name: Upsert advisory PR comment
49+
if: steps.locate.outputs.found == 'true'
50+
env:
51+
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
52+
run: |
53+
packet=artifact/ai-review-packet.json
54+
metadata=artifact/pr-metadata.json
55+
56+
PR_NUMBER=$(jq -r '.pr_number' "$metadata")
57+
HEAD_SHA=$(jq -r '.head_sha' "$metadata")
58+
BASE_SHA=$(jq -r '.base_sha' "$metadata")
59+
TRIGGER_NAME=$(jq -r '.trigger' "$metadata")
60+
61+
marker='<!-- docs-ai-review:packet -->'
62+
changed_count=$(jq '.changed_files | length' "$packet")
63+
high_risk=$(jq -r '.high_risk.required' "$packet")
64+
agent_count=$(jq '.review_agents | length' "$packet")
65+
66+
cat > /tmp/docs-ai-review-comment.md <<EOF
67+
$marker
68+
Docs AI review packet prepared for this PR.
69+
70+
- Trigger: \`$TRIGGER_NAME\`
71+
- Changed files in packet: \`$changed_count\`
72+
- High-risk docs path matched: \`$high_risk\`
73+
- Review agents included: \`$agent_count\`
74+
- Base SHA: \`$BASE_SHA\`
75+
- Head SHA: \`$HEAD_SHA\`
76+
77+
This MVP uploads the packet as a workflow artifact for advisory AI review. AI findings, when produced, are suggestions only; deterministic governance CI remains the blocking signal.
78+
EOF
79+
80+
existing_comment_id=$(
81+
gh api "repos/${{ github.repository }}/issues/${PR_NUMBER}/comments" \
82+
--paginate \
83+
--jq ".[] | select(.user.type == \"Bot\" and (.body | contains(\"$marker\"))) | .id" \
84+
| tail -n 1
85+
)
86+
87+
if [ -n "$existing_comment_id" ]; then
88+
gh api \
89+
--method PATCH \
90+
"repos/${{ github.repository }}/issues/comments/${existing_comment_id}" \
91+
-f body="$(cat /tmp/docs-ai-review-comment.md)"
92+
else
93+
gh api \
94+
--method POST \
95+
"repos/${{ github.repository }}/issues/${PR_NUMBER}/comments" \
96+
-f body="$(cat /tmp/docs-ai-review-comment.md)"
97+
fi

.github/workflows/docs-ai-review.yml

Lines changed: 19 additions & 60 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ on:
44
issue_comment:
55
types: [created]
66
pull_request:
7-
types: [labeled]
7+
types: [labeled, synchronize, reopened]
88
workflow_dispatch:
99
inputs:
1010
pr_number:
@@ -42,6 +42,11 @@ jobs:
4242
github.event_name == 'pull_request' &&
4343
github.event.action == 'labeled' &&
4444
github.event.label.name == 'ai-review-docs'
45+
) ||
46+
(
47+
github.event_name == 'pull_request' &&
48+
(github.event.action == 'synchronize' || github.event.action == 'reopened') &&
49+
contains(github.event.pull_request.labels.*.name, 'ai-review-docs')
4550
)
4651
steps:
4752
- name: Get PR info
@@ -85,6 +90,9 @@ jobs:
8590
- name: Prepare docs AI review packet
8691
env:
8792
BASE_SHA: ${{ steps.pr.outputs.base_sha }}
93+
HEAD_SHA: ${{ steps.pr.outputs.head_sha }}
94+
PR_NUMBER: ${{ steps.pr.outputs.number }}
95+
TRIGGER_NAME: ${{ github.event_name }}
8896
run: |
8997
mkdir -p website-quality-governance/generated
9098
changed_files=$(git diff --name-only "$BASE_SHA" HEAD | paste -sd, -)
@@ -94,68 +102,19 @@ jobs:
94102
--output website-quality-governance/generated/ai-review-packet.json
95103
jq '{schema_version, changed_files, high_risk, review_agents: [.review_agents[].id], sync_groups: [.sync_groups[].sync_group_id]}' \
96104
website-quality-governance/generated/ai-review-packet.json
105+
jq -n \
106+
--arg pr_number "$PR_NUMBER" \
107+
--arg head_sha "$HEAD_SHA" \
108+
--arg base_sha "$BASE_SHA" \
109+
--arg trigger "$TRIGGER_NAME" \
110+
'{pr_number: $pr_number, head_sha: $head_sha, base_sha: $base_sha, trigger: $trigger}' \
111+
> website-quality-governance/generated/pr-metadata.json
97112
98113
- name: Upload review packet artifact
99114
uses: actions/upload-artifact@v4
100115
with:
101116
name: docs-ai-review-packet-${{ steps.pr.outputs.number }}
102-
path: website-quality-governance/generated/ai-review-packet.json
117+
path: |
118+
website-quality-governance/generated/ai-review-packet.json
119+
website-quality-governance/generated/pr-metadata.json
103120
if-no-files-found: error
104-
105-
upsert-advisory-comment:
106-
runs-on: ubuntu-latest
107-
timeout-minutes: 10
108-
needs: prepare-docs-ai-review
109-
if: needs.prepare-docs-ai-review.result == 'success'
110-
permissions:
111-
contents: read
112-
issues: write
113-
pull-requests: read
114-
steps:
115-
- name: Download review packet artifact
116-
uses: actions/download-artifact@v4
117-
with:
118-
name: docs-ai-review-packet-${{ needs.prepare-docs-ai-review.outputs.pr_number }}
119-
path: website-quality-governance/generated
120-
121-
- name: Upsert advisory PR comment
122-
env:
123-
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
124-
PR_NUMBER: ${{ needs.prepare-docs-ai-review.outputs.pr_number }}
125-
HEAD_SHA: ${{ needs.prepare-docs-ai-review.outputs.head_sha }}
126-
BASE_SHA: ${{ needs.prepare-docs-ai-review.outputs.base_sha }}
127-
TRIGGER_NAME: ${{ github.event_name }}
128-
run: |
129-
marker='<!-- docs-ai-review:packet -->'
130-
changed_count=$(jq '.changed_files | length' website-quality-governance/generated/ai-review-packet.json)
131-
high_risk=$(jq -r '.high_risk.required' website-quality-governance/generated/ai-review-packet.json)
132-
agent_count=$(jq '.review_agents | length' website-quality-governance/generated/ai-review-packet.json)
133-
134-
cat > /tmp/docs-ai-review-comment.md <<EOF
135-
$marker
136-
Docs AI review packet prepared for this PR.
137-
138-
- Trigger: \`$TRIGGER_NAME\`
139-
- Changed files in packet: \`$changed_count\`
140-
- High-risk docs path matched: \`$high_risk\`
141-
- Review agents included: \`$agent_count\`
142-
- Base SHA: \`$BASE_SHA\`
143-
- Head SHA: \`$HEAD_SHA\`
144-
145-
This MVP uploads the packet as a workflow artifact for advisory AI review. AI findings, when produced, are suggestions only; deterministic governance CI remains the blocking signal.
146-
EOF
147-
148-
existing_comment_id=$(
149-
gh api repos/${{ github.repository }}/issues/${PR_NUMBER}/comments \
150-
--jq ".[] | select(.user.type == \"Bot\" and (.body | contains(\"$marker\"))) | .id" \
151-
| tail -n 1
152-
)
153-
154-
if [ -n "$existing_comment_id" ]; then
155-
gh api \
156-
--method PATCH \
157-
repos/${{ github.repository }}/issues/comments/${existing_comment_id} \
158-
-f body="$(cat /tmp/docs-ai-review-comment.md)"
159-
else
160-
gh pr comment "$PR_NUMBER" --body-file /tmp/docs-ai-review-comment.md
161-
fi

0 commit comments

Comments
 (0)