-
-
Notifications
You must be signed in to change notification settings - Fork 579
Add GitHub Actions to close spam issues and PRs #765
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
base: master
Are you sure you want to change the base?
Conversation
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.
Pull request overview
This PR adds two GitHub Actions workflows to automatically close issues and pull requests from new users (those with NONE, FIRST_TIMER, or FIRST_TIME_CONTRIBUTOR association) in an attempt to combat spam. The workflows apply a "spam" label, post an explanatory comment, and close the item unless it has an exempt label.
Key changes:
- Auto-close workflow for PRs using
pull_request_targettrigger with appropriate permissions - Auto-close workflow for issues using
issuestrigger with write permissions - Exemption mechanism via labels (allow, do-not-close, not-spam, triage)
Reviewed changes
Copilot reviewed 2 out of 2 changed files in this pull request and generated 8 comments.
| File | Description |
|---|---|
.github/workflows/close-spam-prs.yml |
Workflow to automatically close PRs from new users, with label-based exemptions |
.github/workflows/close-spam-issues.yml |
Workflow to automatically close issues from new users, with label-based exemptions |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| const issue = context.payload.issue | ||
| const assoc = issue.author_association || "" | ||
| const labels = (issue.labels || []).map(l => (typeof l === "string" ? l : l.name)) | ||
| const exemptLabels = new Set(["allow", "do-not-close", "not-spam", "triage"]) | ||
| const isExempt = labels.some(l => exemptLabels.has(l)) | ||
|
|
||
| const newUserAssocs = new Set(["NONE", "FIRST_TIMER", "FIRST_TIME_CONTRIBUTOR"]) | ||
| const isNewUser = newUserAssocs.has(assoc) | ||
|
|
||
| if (!isNewUser || isExempt) { | ||
| core.info(`Not closing. author_association=${assoc}, exempt=${isExempt}`) | ||
| return | ||
| } |
Copilot
AI
Dec 22, 2025
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.
This workflow will automatically close ALL issues from users with NONE, FIRST_TIMER, or FIRST_TIME_CONTRIBUTOR association, not just spam. This means legitimate first-time issue reporters will have their issues automatically closed unless they are manually exempted with a label first. Consider adding additional heuristics to identify actual spam (e.g., issue title/body patterns, length, presence of links) rather than blanket-closing all first-time issues. The current implementation may significantly harm the project's ability to receive bug reports and feature requests from new community members.
| await github.rest.issues.addLabels({ | ||
| owner: context.repo.owner, | ||
| repo: context.repo.repo, | ||
| issue_number: pr.number, | ||
| labels: ["spam"] | ||
| }) |
Copilot
AI
Dec 22, 2025
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.
The workflow attempts to add a "spam" label, but if this label doesn't exist in the repository, the API call will fail with a 422 error. This would prevent the subsequent comment and closing operations from executing due to the lack of error handling. Either ensure the "spam" label exists in the repository before deploying this workflow, or add error handling to gracefully continue if the label doesn't exist.
| await github.rest.issues.addLabels({ | |
| owner: context.repo.owner, | |
| repo: context.repo.repo, | |
| issue_number: pr.number, | |
| labels: ["spam"] | |
| }) | |
| try { | |
| await github.rest.issues.addLabels({ | |
| owner: context.repo.owner, | |
| repo: context.repo.repo, | |
| issue_number: pr.number, | |
| labels: ["spam"] | |
| }) | |
| } catch (error) { | |
| // If the "spam" label does not exist or labeling fails, log a warning and continue. | |
| core.warning(`Failed to add "spam" label to PR #${pr.number}: ${error.message || error}`) | |
| } |
| await github.rest.issues.addLabels({ | ||
| owner: context.repo.owner, | ||
| repo: context.repo.repo, | ||
| issue_number: pr.number, | ||
| labels: ["spam"] | ||
| }) | ||
|
|
||
| await github.rest.issues.createComment({ | ||
| owner: context.repo.owner, | ||
| repo: context.repo.repo, | ||
| issue_number: pr.number, | ||
| body: message | ||
| }) | ||
|
|
||
| await github.rest.pulls.update({ | ||
| owner: context.repo.owner, | ||
| repo: context.repo.repo, | ||
| pull_number: pr.number, | ||
| state: "closed" | ||
| }) |
Copilot
AI
Dec 22, 2025
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.
The workflow lacks error handling for the API calls. If any of the operations (addLabels, createComment, or update) fail, subsequent operations won't execute, potentially leaving the PR in an inconsistent state. For example, the PR might be labeled as spam but not closed, or closed without a comment explaining why. Consider wrapping these operations in a try-catch block to handle failures gracefully, or at minimum ensure the comment is posted before closing the PR so users understand what happened.
| await github.rest.issues.addLabels({ | |
| owner: context.repo.owner, | |
| repo: context.repo.repo, | |
| issue_number: pr.number, | |
| labels: ["spam"] | |
| }) | |
| await github.rest.issues.createComment({ | |
| owner: context.repo.owner, | |
| repo: context.repo.repo, | |
| issue_number: pr.number, | |
| body: message | |
| }) | |
| await github.rest.pulls.update({ | |
| owner: context.repo.owner, | |
| repo: context.repo.repo, | |
| pull_number: pr.number, | |
| state: "closed" | |
| }) | |
| try { | |
| await github.rest.issues.addLabels({ | |
| owner: context.repo.owner, | |
| repo: context.repo.repo, | |
| issue_number: pr.number, | |
| labels: ["spam"] | |
| }) | |
| } catch (error) { | |
| core.warning(`Failed to add spam label to PR #${pr.number}: ${error}`) | |
| } | |
| try { | |
| await github.rest.issues.createComment({ | |
| owner: context.repo.owner, | |
| repo: context.repo.repo, | |
| issue_number: pr.number, | |
| body: message | |
| }) | |
| } catch (error) { | |
| core.warning(`Failed to add spam comment to PR #${pr.number}: ${error}`) | |
| // Do not close the PR if we couldn't explain why. | |
| return | |
| } | |
| try { | |
| await github.rest.pulls.update({ | |
| owner: context.repo.owner, | |
| repo: context.repo.repo, | |
| pull_number: pr.number, | |
| state: "closed" | |
| }) | |
| } catch (error) { | |
| core.warning(`Failed to close PR #${pr.number}: ${error}`) | |
| } |
| await github.rest.issues.addLabels({ | ||
| owner: context.repo.owner, | ||
| repo: context.repo.repo, | ||
| issue_number: pr.number, | ||
| labels: ["spam"] | ||
| }) | ||
|
|
||
| await github.rest.issues.createComment({ | ||
| owner: context.repo.owner, | ||
| repo: context.repo.repo, | ||
| issue_number: pr.number, | ||
| body: message | ||
| }) | ||
|
|
||
| await github.rest.pulls.update({ | ||
| owner: context.repo.owner, | ||
| repo: context.repo.repo, | ||
| pull_number: pr.number, | ||
| state: "closed" | ||
| }) |
Copilot
AI
Dec 22, 2025
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.
The operations to label, comment, and close the PR are not atomic. If a maintainer manually adds an exempt label (like "not-spam") after the workflow starts but before it completes, the PR could still be closed despite the exemption. Consider either: (1) re-checking the labels immediately before closing, or (2) reordering operations to close first and then label (though this has its own trade-offs), or (3) using GitHub's lock API to prevent race conditions during this critical section.
| await github.rest.issues.addLabels({ | ||
| owner: context.repo.owner, | ||
| repo: context.repo.repo, | ||
| issue_number: issue.number, | ||
| labels: ["spam"] | ||
| }) | ||
|
|
||
| await github.rest.issues.createComment({ | ||
| owner: context.repo.owner, | ||
| repo: context.repo.repo, | ||
| issue_number: issue.number, | ||
| body: message | ||
| }) | ||
|
|
||
| await github.rest.issues.update({ | ||
| owner: context.repo.owner, | ||
| repo: context.repo.repo, | ||
| issue_number: issue.number, | ||
| state: "closed" | ||
| }) |
Copilot
AI
Dec 22, 2025
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.
The operations to label, comment, and close the issue are not atomic. If a maintainer manually adds an exempt label (like "not-spam") after the workflow starts but before it completes, the issue could still be closed despite the exemption. Consider either: (1) re-checking the labels immediately before closing, or (2) reordering operations to close first and then label (though this has its own trade-offs), or (3) using GitHub's lock API to prevent race conditions during this critical section.
| await github.rest.issues.addLabels({ | ||
| owner: context.repo.owner, | ||
| repo: context.repo.repo, | ||
| issue_number: issue.number, | ||
| labels: ["spam"] | ||
| }) |
Copilot
AI
Dec 22, 2025
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.
The workflow attempts to add a "spam" label, but if this label doesn't exist in the repository, the API call will fail with a 422 error. This would prevent the subsequent comment and closing operations from executing due to the lack of error handling. Either ensure the "spam" label exists in the repository before deploying this workflow, or add error handling to gracefully continue if the label doesn't exist.
| await github.rest.issues.addLabels({ | |
| owner: context.repo.owner, | |
| repo: context.repo.repo, | |
| issue_number: issue.number, | |
| labels: ["spam"] | |
| }) | |
| try { | |
| await github.rest.issues.addLabels({ | |
| owner: context.repo.owner, | |
| repo: context.repo.repo, | |
| issue_number: issue.number, | |
| labels: ["spam"] | |
| }) | |
| } catch (error) { | |
| core.warning(`Failed to add "spam" label to issue #${issue.number}: ${error.message || error}`) | |
| } |
| await github.rest.issues.addLabels({ | ||
| owner: context.repo.owner, | ||
| repo: context.repo.repo, | ||
| issue_number: issue.number, | ||
| labels: ["spam"] | ||
| }) | ||
|
|
||
| await github.rest.issues.createComment({ | ||
| owner: context.repo.owner, | ||
| repo: context.repo.repo, | ||
| issue_number: issue.number, | ||
| body: message | ||
| }) | ||
|
|
||
| await github.rest.issues.update({ | ||
| owner: context.repo.owner, | ||
| repo: context.repo.repo, | ||
| issue_number: issue.number, | ||
| state: "closed" | ||
| }) |
Copilot
AI
Dec 22, 2025
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.
The workflow lacks error handling for the API calls. If any of the operations (addLabels, createComment, or update) fail, subsequent operations won't execute, potentially leaving the issue in an inconsistent state. For example, the issue might be labeled as spam but not closed, or closed without a comment explaining why. Consider wrapping these operations in a try-catch block to handle failures gracefully, or at minimum ensure the comment is posted before closing the issue so users understand what happened.
| await github.rest.issues.addLabels({ | |
| owner: context.repo.owner, | |
| repo: context.repo.repo, | |
| issue_number: issue.number, | |
| labels: ["spam"] | |
| }) | |
| await github.rest.issues.createComment({ | |
| owner: context.repo.owner, | |
| repo: context.repo.repo, | |
| issue_number: issue.number, | |
| body: message | |
| }) | |
| await github.rest.issues.update({ | |
| owner: context.repo.owner, | |
| repo: context.repo.repo, | |
| issue_number: issue.number, | |
| state: "closed" | |
| }) | |
| try { | |
| await github.rest.issues.addLabels({ | |
| owner: context.repo.owner, | |
| repo: context.repo.repo, | |
| issue_number: issue.number, | |
| labels: ["spam"] | |
| }) | |
| } catch (error) { | |
| core.warning(`Failed to add spam label to issue #${issue.number}: ${error}`) | |
| } | |
| try { | |
| await github.rest.issues.createComment({ | |
| owner: context.repo.owner, | |
| repo: context.repo.repo, | |
| issue_number: issue.number, | |
| body: message | |
| }) | |
| } catch (error) { | |
| core.warning(`Failed to comment on issue #${issue.number} before closing: ${error}`) | |
| } | |
| try { | |
| await github.rest.issues.update({ | |
| owner: context.repo.owner, | |
| repo: context.repo.repo, | |
| issue_number: issue.number, | |
| state: "closed" | |
| }) | |
| } catch (error) { | |
| core.warning(`Failed to close issue #${issue.number}: ${error}`) | |
| } |
| const pr = context.payload.pull_request | ||
| const assoc = pr.author_association || "" | ||
| const labels = (pr.labels || []).map(l => (typeof l === "string" ? l : l.name)) | ||
| const exemptLabels = new Set(["allow", "do-not-close", "not-spam", "triage"]) | ||
| const isExempt = labels.some(l => exemptLabels.has(l)) | ||
|
|
||
| const newUserAssocs = new Set(["NONE", "FIRST_TIMER", "FIRST_TIME_CONTRIBUTOR"]) | ||
| const isNewUser = newUserAssocs.has(assoc) | ||
|
|
||
| if (!isNewUser || isExempt) { | ||
| core.info(`Not closing. author_association=${assoc}, exempt=${isExempt}`) | ||
| return | ||
| } |
Copilot
AI
Dec 22, 2025
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.
This workflow will automatically close ALL pull requests from users with NONE, FIRST_TIMER, or FIRST_TIME_CONTRIBUTOR association, not just spam. This means legitimate first-time contributors will have their PRs automatically closed unless they are manually exempted with a label first. Consider adding additional heuristics to identify actual spam (e.g., PR title/body patterns, changed files, PR size) rather than blanket-closing all first-time contributions. The current implementation may significantly harm the project's ability to accept contributions from new community members.
No description provided.