Skip to content

Cleaner

Cleaner #3341

Workflow file for this run

name: Cleaner
on:
schedule:
# Run every hour at minute 0
- cron: '0 * * * *'
workflow_dispatch:
permissions:
contents: write
issues: write
pull-requests: write
discussions: write
jobs:
cleanup:
runs-on: ubuntu-latest
steps:
- name: Close test issues, PRs, and delete discussions
uses: actions/github-script@v7
with:
script: |
const prefix = "[Custom Engine Test]";
// Close issues with the prefix
const issues = await github.rest.issues.listForRepo({
owner: context.repo.owner,
repo: context.repo.repo,
state: 'open',
per_page: 100
});
for (const issue of issues.data) {
if (issue.title.startsWith(prefix) && !issue.pull_request) {
console.log(`Closing issue: ${issue.title} (#${issue.number})`);
await github.rest.issues.update({
owner: context.repo.owner,
repo: context.repo.repo,
issue_number: issue.number,
state: 'closed'
});
}
}
// Close pull requests with the prefix
const pulls = await github.rest.pulls.list({
owner: context.repo.owner,
repo: context.repo.repo,
state: 'open',
per_page: 100
});
for (const pr of pulls.data) {
if (pr.title.startsWith(prefix)) {
console.log(
`Closing pull request: ${pr.title} (#${pr.number})`
);
await github.rest.pulls.update({
owner: context.repo.owner,
repo: context.repo.repo,
pull_number: pr.number,
state: 'closed'
});
}
}
// Delete discussions with the prefix
try {
const discussionsQuery = `
query($owner: String!, $repo: String!) {
repository(owner: $owner, name: $repo) {
discussions(first: 100, states: [OPEN]) {
nodes {
id
number
title
}
}
}
}
`;
const discussionsResult = await github.graphql(discussionsQuery, {
owner: context.repo.owner,
repo: context.repo.repo,
});
const discussions =
discussionsResult.repository.discussions.nodes || [];
for (const discussion of discussions) {
if (discussion.title.startsWith(prefix)) {
console.log(
`Deleting discussion: ` +
`${discussion.title} (#${discussion.number})`
);
const deleteDiscussionMutation = `
mutation($discussionId: ID!) {
deleteDiscussion(input: { id: $discussionId }) {
discussion {
id
}
}
}
`;
await github.graphql(deleteDiscussionMutation, {
discussionId: discussion.id,
});
}
}
} catch (error) {
// Handle repositories without discussions enabled gracefully
const errorMessage =
error instanceof Error ? error.message : String(error);
if (
errorMessage.includes("Not Found") ||
errorMessage.includes("not found") ||
errorMessage.includes("Could not resolve to a Repository") ||
errorMessage.includes("discussions")
) {
console.log(
"⚠ Cannot delete discussions: " +
"Discussions are not enabled for this repository " +
"or no discussions found"
);
} else {
console.error(`Error deleting discussions: ${errorMessage}`);
throw error;
}
}
// Delete branches starting with "test-safe-outputs-custom-engine/"
const branchPrefix = "test-safe-outputs-custom-engine/";
try {
const branchesResponse = await github.rest.repos.listBranches({
owner: context.repo.owner,
repo: context.repo.repo,
per_page: 100
});
const branchesToDelete = branchesResponse.data.filter(
branch => branch.name.startsWith(branchPrefix)
);
for (const branch of branchesToDelete) {
console.log(`Deleting branch: ${branch.name}`);
try {
await github.rest.git.deleteRef({
owner: context.repo.owner,
repo: context.repo.repo,
ref: `heads/${branch.name}`
});
} catch (deleteError) {
const errorMessage = deleteError instanceof Error
? deleteError.message
: String(deleteError);
console.error(`Failed to delete branch ${branch.name}: ${errorMessage}`);
// Continue with other branches even if one fails
}
}
if (branchesToDelete.length > 0) {
console.log(`Processed ${branchesToDelete.length} branches with prefix "${branchPrefix}"`);
} else {
console.log(`No branches found with prefix "${branchPrefix}"`);
}
} catch (error) {
const errorMessage = error instanceof Error ? error.message : String(error);
console.error(`Error listing branches: ${errorMessage}`);
// Don't throw error - we want other cleanup operations to continue
}