Skip to content
Merged
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
84 changes: 84 additions & 0 deletions .github/workflows/pr-issue-sync.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
name: Sync PR Metadata from Linked Issues

on:
pull_request:
types: [opened, edited, synchronize]

jobs:
sync-metadata:
runs-on: ubuntu-latest

permissions:
pull-requests: write
issues: read
contents: read

steps:
- name: Extract Linked Issues
id: extract
uses: actions/github-script@v7
with:
script: |
const body = context.payload.pull_request.body || "";
const issuePattern = /#(\d+)/g;
Copy link

Copilot AI Sep 25, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The regex pattern will match any '#' followed by digits, potentially capturing false positives like '#123' in code snippets or unrelated contexts. Consider using a more specific pattern that looks for common issue linking phrases like 'fixes #123', 'closes #123', or 'resolves #123'.

Suggested change
const issuePattern = /#(\d+)/g;
const issuePattern = /\b(?:fixe?s|close[sd]?|resolve[sd]?)[:\s]+#(\d+)/gi;

Copilot uses AI. Check for mistakes.
Copy link

Copilot AI Sep 25, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The regex pattern /#(\d+)/g will match any #number format, but GitHub's issue linking requires specific keywords like 'fixes', 'closes', 'resolves' followed by the issue number. This could incorrectly match unrelated hash numbers in the PR body.

Suggested change
const issuePattern = /#(\d+)/g;
// Match "fixes #123", "closes: #456", "resolves #789", etc. (case-insensitive)
const issuePattern = /\b(?:fixe?[sd]?|close[sd]?|resolve[sd]?)\s*[: ]+\s*#(\d+)/gi;

Copilot uses AI. Check for mistakes.
const matches = [...body.matchAll(issuePattern)];
const issueNumbers = matches.map(m => parseInt(m[1]));
core.setOutput("issues", JSON.stringify(issueNumbers));

- name: Sync Metadata into PR Body
if: steps.extract.outputs.issues != '[]'
uses: actions/github-script@v7
with:
script: |
const issues = JSON.parse(core.getInput("issues"));
Copy link

Copilot AI Sep 25, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The script attempts to read input 'issues' but it was set as an output in the previous step. This should be steps.extract.outputs.issues instead of core.getInput('issues').

Suggested change
const issues = JSON.parse(core.getInput("issues"));
const issues = JSON.parse(inputs.issues);

Copilot uses AI. Check for mistakes.
const pr = context.payload.pull_request;

let combinedLabels = [];
let combinedAssignees = [];
let combinedMilestones = [];

for (const number of issues) {
try {
const issue = await github.rest.issues.get({
...context.repo,
issue_number: number
});

const labels = issue.data.labels.map(l => l.name);
const assignees = issue.data.assignees.map(a => a.login);
const milestone = issue.data.milestone ? issue.data.milestone.title : null;

combinedLabels.push(...labels);
combinedAssignees.push(...assignees);
if (milestone) combinedMilestones.push(milestone);

} catch (err) {
console.log(`Could not sync from issue #${number}:`, err.message);
}
}

// Deduplicate
combinedLabels = [...new Set(combinedLabels)];
combinedAssignees = [...new Set(combinedAssignees)];
combinedMilestones = [...new Set(combinedMilestones)];

const metadataBlock =
`\n---\n` +
`### Synced data from Linked Issues\n\n` +
Copy link

Copilot AI Sep 25, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There are extra spaces between '###' and 'Synced' which creates non-standard markdown formatting. This should be '### Synced data from Linked Issues' with a single space.

Copilot uses AI. Check for mistakes.
`**Labels:**\n${combinedLabels.length ? combinedLabels.map(l => `- ${l}`).join("\n") : "- None"}\n\n` +
`**Assignees:**\n${combinedAssignees.length ? combinedAssignees.map(a => `- ${a}`).join("\n") : "- None"}\n\n` +
`**Milestones:**\n${combinedMilestones.length ? combinedMilestones.map(m => `- ${m}`).join("\n") : "- None"}\n`;

let newBody = pr.body || "";

// Remove old metadata block if exists
newBody = newBody.replace(/---\n### Synced Metadata[\s\S]*$/, "");
Copy link

Copilot AI Sep 25, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The regex pattern searches for 'Synced Metadata' but the actual header being created is 'Synced data from Linked Issues' (line 67). This will result in duplicate metadata blocks being appended instead of replacing the existing one.

Suggested change
newBody = newBody.replace(/---\n### Synced Metadata[\s\S]*$/, "");
newBody = newBody.replace(/---\n### Synced data from Linked Issues[\s\S]*$/, "");

Copilot uses AI. Check for mistakes.

// Append fresh metadata block
newBody += metadataBlock;

await github.rest.pulls.update({
...context.repo,
pull_number: pr.number,
body: newBody
});
Loading