Skip to content

Conversation

@cpholguera
Copy link
Collaborator

No description provided.

@cpholguera cpholguera changed the title Create close-spam-issues.yml Add GitHub Actions to close spam issues and PRs Dec 22, 2025
Copy link

Copilot AI left a 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_target trigger with appropriate permissions
  • Auto-close workflow for issues using issues trigger 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.

Comment on lines +18 to +30
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
}
Copy link

Copilot AI Dec 22, 2025

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.

Copilot uses AI. Check for mistakes.
Comment on lines +37 to +42
await github.rest.issues.addLabels({
owner: context.repo.owner,
repo: context.repo.repo,
issue_number: pr.number,
labels: ["spam"]
})
Copy link

Copilot AI Dec 22, 2025

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.

Suggested change
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}`)
}

Copilot uses AI. Check for mistakes.
Comment on lines +37 to +56
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"
})
Copy link

Copilot AI Dec 22, 2025

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.

Suggested change
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}`)
}

Copilot uses AI. Check for mistakes.
Comment on lines +37 to +56
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"
})
Copy link

Copilot AI Dec 22, 2025

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.

Copilot uses AI. Check for mistakes.
Comment on lines +36 to +55
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"
})
Copy link

Copilot AI Dec 22, 2025

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.

Copilot uses AI. Check for mistakes.
Comment on lines +36 to +41
await github.rest.issues.addLabels({
owner: context.repo.owner,
repo: context.repo.repo,
issue_number: issue.number,
labels: ["spam"]
})
Copy link

Copilot AI Dec 22, 2025

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.

Suggested change
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}`)
}

Copilot uses AI. Check for mistakes.
Comment on lines +36 to +55
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"
})
Copy link

Copilot AI Dec 22, 2025

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.

Suggested change
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}`)
}

Copilot uses AI. Check for mistakes.
Comment on lines +19 to +31
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
}
Copy link

Copilot AI Dec 22, 2025

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.

Copilot uses AI. Check for mistakes.
@cpholguera cpholguera marked this pull request as draft December 22, 2025 15:21
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants