Skip to content

Commit 7244dbf

Browse files
authored
fix(ci): split impact workflow for fork PR compatibility (#951)
* fix(ci): split impact workflow for fork PR compatibility Fork PRs receive a read-only GITHUB_TOKEN that cannot comment on PRs. Split into two workflows: the analysis runs on pull_request (read-only), uploads results as an artifact, and a second workflow_run-triggered workflow downloads the artifact and posts the comment with write access. * fix(ci): add actions:read permission and concurrency guard to impact comment workflow (#951) - Add actions:read permission required by actions/download-artifact@v4 when downloading from a different workflow run (cross-run artifact API calls require this scope). Without it, the download step would 403 and the comment workflow would be entirely broken. - Add a per-head-SHA concurrency group with cancel-in-progress to prevent duplicate 'Codegraph Impact Analysis' comments when two analysis runs complete in quick succession for the same PR head. - Add a header comment documenting why the workflow is split from codegraph-impact.yml (fork PR permission limitation) for future maintainers.
1 parent 7a47ce2 commit 7244dbf

2 files changed

Lines changed: 83 additions & 40 deletions

File tree

Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,74 @@
1+
# This workflow posts the impact analysis comment on PRs.
2+
# It is split from codegraph-impact.yml so fork PRs work: the `pull_request`
3+
# event from a fork provides a read-only GITHUB_TOKEN, which cannot post PR
4+
# comments. Running here via `workflow_run` gives us a write-scoped token
5+
# without exposing it to untrusted fork code. See GitHub's fork-safe pattern:
6+
# https://securitylab.github.com/resources/github-actions-preventing-pwn-requests/
7+
name: Codegraph Impact Comment
8+
on:
9+
workflow_run:
10+
workflows: ['Codegraph Impact Analysis']
11+
types: [completed]
12+
13+
permissions:
14+
pull-requests: write
15+
# Required by actions/download-artifact@v4 when downloading from another
16+
# workflow run (cross-run artifact API calls require actions: read).
17+
actions: read
18+
19+
concurrency:
20+
# Prevent duplicate comments if two analysis runs complete in quick succession
21+
# for the same PR head. Keyed by the head SHA of the triggering workflow run.
22+
group: codegraph-impact-comment-${{ github.event.workflow_run.head_sha }}
23+
cancel-in-progress: true
24+
25+
jobs:
26+
comment:
27+
runs-on: ubuntu-latest
28+
if: github.event.workflow_run.conclusion == 'success'
29+
steps:
30+
- name: Download impact artifact
31+
uses: actions/download-artifact@v4
32+
with:
33+
name: codegraph-impact
34+
run-id: ${{ github.event.workflow_run.id }}
35+
github-token: ${{ secrets.GITHUB_TOKEN }}
36+
- name: Comment on PR
37+
uses: actions/github-script@v9
38+
with:
39+
script: |
40+
const fs = require('fs');
41+
const prNumber = parseInt(fs.readFileSync('pr-number.txt', 'utf-8').trim(), 10);
42+
const impact = JSON.parse(fs.readFileSync('impact.json', 'utf-8'));
43+
if (!impact.summary || (impact.summary.functionsChanged === 0 && impact.summary.callersAffected === 0)) {
44+
console.log('No impact data to report.');
45+
return;
46+
}
47+
const body = `## Codegraph Impact Analysis\n\n` +
48+
`**${impact.summary.functionsChanged} functions changed** → ` +
49+
`**${impact.summary.callersAffected} callers affected** across ` +
50+
`**${impact.summary.filesAffected} files**\n\n` +
51+
(impact.affectedFunctions || []).slice(0, 20).map(f =>
52+
`- \`${f.name}\` in \`${f.file}:${f.line}\` (${f.transitiveCallers} transitive callers)`
53+
).join('\n');
54+
const comments = await github.paginate(github.rest.issues.listComments, {
55+
owner: context.repo.owner,
56+
repo: context.repo.repo,
57+
issue_number: prNumber,
58+
});
59+
const existing = comments.find(c => c.body.startsWith('## Codegraph Impact Analysis'));
60+
if (existing) {
61+
await github.rest.issues.updateComment({
62+
owner: context.repo.owner,
63+
repo: context.repo.repo,
64+
comment_id: existing.id,
65+
body,
66+
});
67+
} else {
68+
await github.rest.issues.createComment({
69+
owner: context.repo.owner,
70+
repo: context.repo.repo,
71+
issue_number: prNumber,
72+
body,
73+
});
74+
}

.github/workflows/codegraph-impact.yml

Lines changed: 9 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,6 @@ on: [pull_request]
33

44
permissions:
55
contents: read
6-
pull-requests: write
76

87
concurrency:
98
group: codegraph-impact-${{ github.event.pull_request.number }}
@@ -41,43 +40,13 @@ jobs:
4140
run: node dist/cli.js build || (rm -rf .codegraph && node dist/cli.js build --no-incremental)
4241
- name: Run impact analysis
4342
run: node dist/cli.js diff-impact origin/${{ github.base_ref }} --json -T > impact.json
44-
- name: Comment on PR
45-
if: success()
46-
uses: actions/github-script@v9
43+
- name: Save PR number
44+
run: echo "${{ github.event.pull_request.number }}" > pr-number.txt
45+
- name: Upload impact artifact
46+
uses: actions/upload-artifact@v4
4747
with:
48-
script: |
49-
const fs = require('fs');
50-
const impact = JSON.parse(fs.readFileSync('impact.json', 'utf-8'));
51-
if (!impact.summary || (impact.summary.functionsChanged === 0 && impact.summary.callersAffected === 0)) {
52-
console.log('No impact data to report.');
53-
return;
54-
}
55-
const body = `## Codegraph Impact Analysis\n\n` +
56-
`**${impact.summary.functionsChanged} functions changed** → ` +
57-
`**${impact.summary.callersAffected} callers affected** across ` +
58-
`**${impact.summary.filesAffected} files**\n\n` +
59-
(impact.affectedFunctions || []).slice(0, 20).map(f =>
60-
`- \`${f.name}\` in \`${f.file}:${f.line}\` (${f.transitiveCallers} transitive callers)`
61-
).join('\n');
62-
// Update existing comment or create new one
63-
const comments = await github.paginate(github.rest.issues.listComments, {
64-
owner: context.repo.owner,
65-
repo: context.repo.repo,
66-
issue_number: context.issue.number,
67-
});
68-
const existing = comments.find(c => c.body.startsWith('## Codegraph Impact Analysis'));
69-
if (existing) {
70-
await github.rest.issues.updateComment({
71-
owner: context.repo.owner,
72-
repo: context.repo.repo,
73-
comment_id: existing.id,
74-
body,
75-
});
76-
} else {
77-
await github.rest.issues.createComment({
78-
owner: context.repo.owner,
79-
repo: context.repo.repo,
80-
issue_number: context.issue.number,
81-
body,
82-
});
83-
}
48+
name: codegraph-impact
49+
path: |
50+
impact.json
51+
pr-number.txt
52+
retention-days: 1

0 commit comments

Comments
 (0)