-
Notifications
You must be signed in to change notification settings - Fork 1
ci: add automatic PR labeling from linked issues #67
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| @@ -0,0 +1,65 @@ | ||||||||||||||||||||
| // Filter function for labels we do not want on PRs automatically. | ||||||||||||||||||||
| function shouldIncludeLabel(label) { | ||||||||||||||||||||
| const isStatus = label.startsWith("S-"); | ||||||||||||||||||||
| const isPreventStale = label === "M-prevent-stale"; | ||||||||||||||||||||
| const isDifficulty = label.startsWith("D-"); | ||||||||||||||||||||
|
|
||||||||||||||||||||
| return !isStatus && !isPreventStale && !isDifficulty; | ||||||||||||||||||||
| } | ||||||||||||||||||||
|
|
||||||||||||||||||||
| // Get the issue number from an issue link in the forms | ||||||||||||||||||||
| // `<keyword> <issue url>` or `<keyword> #<issue number>`. | ||||||||||||||||||||
| function getIssueLink(repoUrl, body) { | ||||||||||||||||||||
| if (!body) return undefined; | ||||||||||||||||||||
|
|
||||||||||||||||||||
| const urlPattern = new RegExp( | ||||||||||||||||||||
| `(close|closes|closed|fix|fixes|fixed|resolve|resolves|resolved) ${repoUrl}/issues/(?<issue_number>\\d+)`, | ||||||||||||||||||||
| "i" | ||||||||||||||||||||
| ); | ||||||||||||||||||||
| const issuePattern = new RegExp( | ||||||||||||||||||||
| `(close|closes|closed|fix|fixes|fixed|resolve|resolves|resolved) #(?<issue_number>\\d+)`, | ||||||||||||||||||||
| "i" | ||||||||||||||||||||
| ); | ||||||||||||||||||||
|
|
||||||||||||||||||||
| const urlRe = body.match(urlPattern); | ||||||||||||||||||||
| const issueRe = body.match(issuePattern); | ||||||||||||||||||||
| if (urlRe?.groups?.issue_number) { | ||||||||||||||||||||
| return urlRe.groups.issue_number; | ||||||||||||||||||||
| } else { | ||||||||||||||||||||
| return issueRe?.groups?.issue_number; | ||||||||||||||||||||
| } | ||||||||||||||||||||
| } | ||||||||||||||||||||
|
|
||||||||||||||||||||
| module.exports = async ({ github, context }) => { | ||||||||||||||||||||
| try { | ||||||||||||||||||||
| const prNumber = context.payload.pull_request.number; | ||||||||||||||||||||
| const prBody = context.payload.pull_request.body; | ||||||||||||||||||||
| const repo = context.repo; | ||||||||||||||||||||
|
|
||||||||||||||||||||
| const repoUrl = context.payload.repository.html_url; | ||||||||||||||||||||
| const issueNumber = getIssueLink(repoUrl, prBody); | ||||||||||||||||||||
| if (!issueNumber) { | ||||||||||||||||||||
| console.log("No issue reference found in PR description."); | ||||||||||||||||||||
| return; | ||||||||||||||||||||
| } | ||||||||||||||||||||
|
|
||||||||||||||||||||
| const issue = await github.rest.issues.get({ | ||||||||||||||||||||
| ...repo, | ||||||||||||||||||||
| issue_number: issueNumber, | ||||||||||||||||||||
| }); | ||||||||||||||||||||
|
|
||||||||||||||||||||
| const issueLabels = issue.data.labels | ||||||||||||||||||||
| .map((label) => label.name) | ||||||||||||||||||||
| .filter(shouldIncludeLabel); | ||||||||||||||||||||
| if (issueLabels.length > 0) { | ||||||||||||||||||||
| await github.rest.issues.addLabels({ | ||||||||||||||||||||
| ...repo, | ||||||||||||||||||||
| issue_number: prNumber, | ||||||||||||||||||||
| labels: issueLabels, | ||||||||||||||||||||
| }); | ||||||||||||||||||||
| } | ||||||||||||||||||||
| } catch (err) { | ||||||||||||||||||||
| console.error("Failed to label PR"); | ||||||||||||||||||||
| console.error(err); | ||||||||||||||||||||
| } | ||||||||||||||||||||
|
Comment on lines
+61
to
+64
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Do not swallow labeling failures silently. Line 61-Line 64 only logs errors. If GitHub API calls fail, the workflow still appears successful and masking this breaks reliability of auto-labeling. ✅ Suggested fix } catch (err) {
console.error("Failed to label PR");
console.error(err);
+ throw err;
}
};📝 Committable suggestion
Suggested change
🤖 Prompt for AI Agents |
||||||||||||||||||||
| }; | ||||||||||||||||||||
| Original file line number | Diff line number | Diff line change | ||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| @@ -0,0 +1,23 @@ | ||||||||||||||
| name: Label PRs | ||||||||||||||
|
|
||||||||||||||
| on: | ||||||||||||||
| pull_request: | ||||||||||||||
| types: [opened] | ||||||||||||||
|
|
||||||||||||||
| jobs: | ||||||||||||||
| label_prs: | ||||||||||||||
| runs-on: ubuntu-latest | ||||||||||||||
| permissions: | ||||||||||||||
| issues: write | ||||||||||||||
| pull-requests: write | ||||||||||||||
|
Comment on lines
+10
to
+12
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🧩 Analysis chain🏁 Script executed: find .github/workflows -name "*label-pr*" -type fRepository: morph-l2/morph-reth Length of output: 93 🏁 Script executed: cat -n .github/workflows/label-pr.ymlRepository: morph-l2/morph-reth Length of output: 688 🏁 Script executed: cat -n .github/assets/label_pr.jsRepository: morph-l2/morph-reth Length of output: 2433 🌐 Web query:
💡 Result: Yes, GitHub Actions workflows require pull-requests: write permission to add labels to pull requests using the GITHUB_TOKEN. To add labels to a pull request (via the Issues API endpoint POST /repos/{owner}/{repo}/issues/{issue_number}/labels, since PRs are issues), declare the permission in your workflow YAML: permissions: pull-requests: write Example workflow step using gh pr edit or REST API: - name: Add label to PR run: gh pr edit ${{ github.event.pull_request.number }} --add-label "your-label" env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} This grants the GITHUB_TOKEN write access for pull requests. For labeler actions or creating new labels (if they don't exist), issues: write may also be needed alongside pull-requests: write, as PRs leverage the Issues API internally. Official examples in GitHub Docs use issues: write for issue labeling (applicable to PRs), but pull-requests: write is standard and sufficient for adding existing labels to PRs per actions/labeler documentation and community usage. For forked PRs, use pull_request_target trigger with explicit permissions for write access. Citations:
🌐 Web query:
💡 Result: The GitHub REST API endpoint for adding labels to issues or pull requests is POST /repos/{owner}/{repo}/issues/{issue_number}/labels. Pull requests use the issues endpoints since every pull request is an issue. Permissions required: - For fine-grained personal access tokens (PATs): issues:write or pull-requests:write permission. - For GitHub Apps: issues:write or pull-requests:write permission. - For classic PATs: repo scope (provides push access to the repository). - Users must have write (push) access to the repository. Without sufficient permissions, label changes are silently dropped or the request fails with 403/404. In GitHub Actions, use permissions: { issues: write } or { pull-requests: write }. Citations:
Tighten job token scope to least privilege. This job labels pull requests via the Issues API; 🔐 Suggested permissions block permissions:
+ contents: read
issues: write
- pull-requests: write📝 Committable suggestion
Suggested change
🤖 Prompt for AI Agents |
||||||||||||||
| steps: | ||||||||||||||
| - uses: actions/checkout@v4 | ||||||||||||||
| with: | ||||||||||||||
| fetch-depth: 0 | ||||||||||||||
|
|
||||||||||||||
| - name: Label PRs | ||||||||||||||
| uses: actions/github-script@v8 | ||||||||||||||
| with: | ||||||||||||||
| script: | | ||||||||||||||
| const label_pr = require('./.github/assets/label_pr.js') | ||||||||||||||
| await label_pr({github, context}) | ||||||||||||||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Make issue-link regex more robust to formatting and URL matching.
Current patterns are fragile (single literal space) and
repoUrlis injected unescaped into a regex. This can miss references in common PR body formatting and allow unintended matches.💡 Proposed hardening
function getIssueLink(repoUrl, body) { if (!body) return undefined; + const escapedRepoUrl = repoUrl.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"); const urlPattern = new RegExp( - `(close|closes|closed|fix|fixes|fixed|resolve|resolves|resolved) ${repoUrl}/issues/(?<issue_number>\\d+)`, + `\\b(close|closes|closed|fix|fixes|fixed|resolve|resolves|resolved)\\b\\s+${escapedRepoUrl}/issues/(?<issue_number>\\d+)\\b`, "i" ); const issuePattern = new RegExp( - `(close|closes|closed|fix|fixes|fixed|resolve|resolves|resolved) #(?<issue_number>\\d+)`, + `\\b(close|closes|closed|fix|fixes|fixed|resolve|resolves|resolved)\\b\\s+#(?<issue_number>\\d+)\\b`, "i" );📝 Committable suggestion
🤖 Prompt for AI Agents