Skip to content

Commit d552951

Browse files
feat: Add workflow to mirror issue labels to pull requests
1 parent 5a2ad4a commit d552951

1 file changed

Lines changed: 87 additions & 0 deletions

File tree

Lines changed: 87 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,87 @@
1+
name: Mirror Issue Labels to PR
2+
3+
on:
4+
pull_request:
5+
types: [opened, reopened, ready_for_review]
6+
7+
permissions: {}
8+
9+
concurrency:
10+
group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }}
11+
cancel-in-progress: true
12+
13+
jobs:
14+
mirror:
15+
name: Mirror linked issue labels to PR
16+
if: >-
17+
contains(github.event.pull_request.labels.*.name, 'coding-agent') ||
18+
github.event.pull_request.user.login == 'Copilot' ||
19+
github.actor == 'copilot-swe-agent[bot]'
20+
runs-on: ubuntu-latest
21+
permissions:
22+
pull-requests: write
23+
issues: read
24+
steps:
25+
- name: Copy labels from linked issue(s)
26+
uses: actions/github-script@v7
27+
with:
28+
script: |
29+
const body = context.payload.pull_request.body || '';
30+
31+
// Strip HTML comments and fenced code blocks before matching, so
32+
// example "Closes #N" lines in template guidance don't trigger.
33+
const stripped = body
34+
.replace(/<!--[\s\S]*?-->/g, '')
35+
.replace(/```[\s\S]*?```/g, '');
36+
37+
// Match "Closes #N", "Fixes #N", "Resolves #N" (case-insensitive,
38+
// with optional ing/d/s suffixes). Only same-repo references are mirrored.
39+
const pattern = /\b(?:close[sd]?|fix(?:e[sd])?|resolve[sd]?)\s+#(\d+)\b/gi;
40+
const issueNumbers = new Set();
41+
let m;
42+
while ((m = pattern.exec(stripped)) !== null) {
43+
issueNumbers.add(parseInt(m[1], 10));
44+
}
45+
46+
if (issueNumbers.size === 0) {
47+
core.info('No "Closes #N" references found in PR body; nothing to mirror.');
48+
return;
49+
}
50+
51+
const prNumber = context.payload.pull_request.number;
52+
const existingLabels = new Set(
53+
(context.payload.pull_request.labels || []).map(l => l.name)
54+
);
55+
56+
const toAdd = new Set();
57+
for (const num of issueNumbers) {
58+
try {
59+
const { data: issue } = await github.rest.issues.get({
60+
owner: context.repo.owner,
61+
repo: context.repo.repo,
62+
issue_number: num,
63+
});
64+
for (const label of issue.labels || []) {
65+
const name = typeof label === 'string' ? label : label.name;
66+
if (!name) continue;
67+
if (existingLabels.has(name)) continue;
68+
toAdd.add(name);
69+
}
70+
} catch (err) {
71+
core.warning(`Failed to fetch issue #${num}: ${err.message}`);
72+
}
73+
}
74+
75+
if (toAdd.size === 0) {
76+
core.info('Linked issue(s) had no new labels to mirror.');
77+
return;
78+
}
79+
80+
const labels = Array.from(toAdd);
81+
await github.rest.issues.addLabels({
82+
owner: context.repo.owner,
83+
repo: context.repo.repo,
84+
issue_number: prNumber,
85+
labels,
86+
});
87+
core.info(`Mirrored ${labels.length} label(s) to PR: ${labels.join(', ')}`);

0 commit comments

Comments
 (0)