mirror of
https://github.com/samsonjs/vibetunnel.git
synced 2026-06-28 05:29:29 +00:00
Fix Claude bot creating multiple PR comments
- Add automatic cleanup of old Claude comments after each review - Create reusable cleanup script that intelligently handles different comment types - Keep only the most recent successful review visible - Collapse (not delete) old reviews, errors, and status messages - Add manual cleanup workflow that can be triggered or run on schedule - Preserve comment history while keeping PRs readable This solves the issue where Claude creates new comments instead of updating existing ones, since the anthropics/claude-code-action@beta doesn't support comment updates natively.
This commit is contained in:
parent
986154da7c
commit
c24749458c
3 changed files with 226 additions and 1 deletions
123
.github/scripts/cleanup-claude-comments.js
vendored
Normal file
123
.github/scripts/cleanup-claude-comments.js
vendored
Normal file
|
|
@ -0,0 +1,123 @@
|
|||
#!/usr/bin/env node
|
||||
|
||||
/**
|
||||
* Script to clean up multiple Claude bot comments on a PR
|
||||
* Keeps only the most recent successful review and collapses others
|
||||
*/
|
||||
|
||||
async function cleanupClaudeComments({ github, context, core }) {
|
||||
const { owner, repo } = context.repo;
|
||||
const issue_number = context.issue.number;
|
||||
|
||||
try {
|
||||
// Get all comments on the PR
|
||||
const allComments = [];
|
||||
let page = 1;
|
||||
let hasMore = true;
|
||||
|
||||
while (hasMore) {
|
||||
const { data } = await github.rest.issues.listComments({
|
||||
owner,
|
||||
repo,
|
||||
issue_number,
|
||||
per_page: 100,
|
||||
page
|
||||
});
|
||||
|
||||
allComments.push(...data);
|
||||
hasMore = data.length === 100;
|
||||
page++;
|
||||
}
|
||||
|
||||
// Filter Claude bot comments
|
||||
const claudeComments = allComments
|
||||
.filter(comment =>
|
||||
comment.user.login === 'claude[bot]' ||
|
||||
comment.user.login === 'claude' ||
|
||||
(comment.user.type === 'Bot' && comment.body.includes('Claude'))
|
||||
)
|
||||
.sort((a, b) => new Date(b.created_at) - new Date(a.created_at));
|
||||
|
||||
if (claudeComments.length <= 1) {
|
||||
core.info(`Found ${claudeComments.length} Claude comments, no cleanup needed`);
|
||||
return;
|
||||
}
|
||||
|
||||
core.info(`Found ${claudeComments.length} Claude comments, cleaning up...`);
|
||||
|
||||
// Categorize comments
|
||||
const successfulReviews = [];
|
||||
const errorComments = [];
|
||||
const statusComments = [];
|
||||
|
||||
for (const comment of claudeComments) {
|
||||
if (comment.body.includes('Claude finished') && comment.body.includes('## 📋 Summary')) {
|
||||
successfulReviews.push(comment);
|
||||
} else if (comment.body.includes('Claude encountered an error')) {
|
||||
errorComments.push(comment);
|
||||
} else if (comment.body.includes('Claude Code is analyzing')) {
|
||||
statusComments.push(comment);
|
||||
}
|
||||
}
|
||||
|
||||
// Keep the most recent successful review visible
|
||||
const commentsToCollapse = [];
|
||||
let keptReview = false;
|
||||
|
||||
if (successfulReviews.length > 0) {
|
||||
// Keep the first (most recent) successful review
|
||||
keptReview = true;
|
||||
commentsToCollapse.push(...successfulReviews.slice(1));
|
||||
}
|
||||
|
||||
// Collapse all error and status comments
|
||||
commentsToCollapse.push(...errorComments, ...statusComments);
|
||||
|
||||
// If no successful review, keep the most recent comment of any type
|
||||
if (!keptReview && claudeComments.length > 0) {
|
||||
commentsToCollapse.push(...claudeComments.slice(1));
|
||||
}
|
||||
|
||||
// Process comments to collapse
|
||||
for (const comment of commentsToCollapse) {
|
||||
try {
|
||||
const timestamp = new Date(comment.created_at).toLocaleString();
|
||||
const commentType =
|
||||
comment.body.includes('encountered an error') ? 'error' :
|
||||
comment.body.includes('is analyzing') ? 'status' :
|
||||
comment.body.includes('finished') ? 'review' : 'comment';
|
||||
|
||||
// Collapse the comment
|
||||
await github.rest.issues.updateComment({
|
||||
owner,
|
||||
repo,
|
||||
comment_id: comment.id,
|
||||
body: `<details><summary>Claude ${commentType} from ${timestamp} (outdated - click to expand)</summary>\n\n${comment.body}\n</details>`
|
||||
});
|
||||
|
||||
core.info(`Collapsed Claude ${commentType} comment ${comment.id} from ${timestamp}`);
|
||||
} catch (error) {
|
||||
// If update fails, try to delete (might not have permission)
|
||||
try {
|
||||
await github.rest.issues.deleteComment({
|
||||
owner,
|
||||
repo,
|
||||
comment_id: comment.id
|
||||
});
|
||||
core.info(`Deleted Claude comment ${comment.id}`);
|
||||
} catch (deleteError) {
|
||||
core.warning(`Failed to update or delete comment ${comment.id}: ${error.message}`);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
core.info(`Cleanup complete. Collapsed ${commentsToCollapse.length} comments`);
|
||||
|
||||
} catch (error) {
|
||||
core.error(`Failed to cleanup Claude comments: ${error.message}`);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
// Export for use in GitHub Actions
|
||||
module.exports = cleanupClaudeComments;
|
||||
15
.github/workflows/claude-code-review.yml
vendored
15
.github/workflows/claude-code-review.yml
vendored
|
|
@ -148,4 +148,17 @@ jobs:
|
|||
HEAD_BRANCH: ${{ github.event.pull_request.head.ref }}
|
||||
CHANGED_FILES: ${{ github.event.pull_request.changed_files }}
|
||||
ADDITIONS: ${{ github.event.pull_request.additions }}
|
||||
DELETIONS: ${{ github.event.pull_request.deletions }}
|
||||
DELETIONS: ${{ github.event.pull_request.deletions }}
|
||||
|
||||
- name: Clean up old Claude comments
|
||||
if: steps.check-review.outputs.skip != 'true'
|
||||
uses: actions/github-script@v7
|
||||
with:
|
||||
github-token: ${{ secrets.GITHUB_TOKEN }}
|
||||
script: |
|
||||
// Wait a bit to ensure the new comment is posted
|
||||
await new Promise(resolve => setTimeout(resolve, 5000));
|
||||
|
||||
// Use the cleanup script
|
||||
const cleanup = require('./.github/scripts/cleanup-claude-comments.js');
|
||||
await cleanup({ github, context, core });
|
||||
89
.github/workflows/cleanup-claude-comments.yml
vendored
Normal file
89
.github/workflows/cleanup-claude-comments.yml
vendored
Normal file
|
|
@ -0,0 +1,89 @@
|
|||
name: Cleanup Claude Comments
|
||||
|
||||
on:
|
||||
# Manual trigger
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
pr_number:
|
||||
description: 'Pull Request number to clean up'
|
||||
required: true
|
||||
type: number
|
||||
|
||||
# Also run when PR is closed or merged
|
||||
pull_request:
|
||||
types: [closed]
|
||||
|
||||
# Run on a schedule to clean up old PRs
|
||||
schedule:
|
||||
- cron: '0 0 * * 0' # Weekly on Sunday
|
||||
|
||||
jobs:
|
||||
cleanup:
|
||||
runs-on: ubuntu-latest
|
||||
permissions:
|
||||
pull-requests: write
|
||||
|
||||
steps:
|
||||
- name: Checkout scripts
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
sparse-checkout: |
|
||||
.github/scripts/cleanup-claude-comments.js
|
||||
|
||||
- name: Cleanup Claude comments on specific PR
|
||||
if: github.event_name == 'workflow_dispatch'
|
||||
uses: actions/github-script@v7
|
||||
with:
|
||||
github-token: ${{ secrets.GITHUB_TOKEN }}
|
||||
script: |
|
||||
const cleanup = require('./.github/scripts/cleanup-claude-comments.js');
|
||||
|
||||
// Override context for manual trigger
|
||||
const customContext = {
|
||||
...context,
|
||||
issue: { number: ${{ github.event.inputs.pr_number }} }
|
||||
};
|
||||
|
||||
await cleanup({ github, context: customContext, core });
|
||||
|
||||
- name: Cleanup Claude comments on closed PR
|
||||
if: github.event_name == 'pull_request'
|
||||
uses: actions/github-script@v7
|
||||
with:
|
||||
github-token: ${{ secrets.GITHUB_TOKEN }}
|
||||
script: |
|
||||
const cleanup = require('./.github/scripts/cleanup-claude-comments.js');
|
||||
await cleanup({ github, context, core });
|
||||
|
||||
- name: Cleanup Claude comments on all recent PRs
|
||||
if: github.event_name == 'schedule'
|
||||
uses: actions/github-script@v7
|
||||
with:
|
||||
github-token: ${{ secrets.GITHUB_TOKEN }}
|
||||
script: |
|
||||
const cleanup = require('./.github/scripts/cleanup-claude-comments.js');
|
||||
|
||||
// Get recent PRs
|
||||
const { data: prs } = await github.rest.pulls.list({
|
||||
owner: context.repo.owner,
|
||||
repo: context.repo.repo,
|
||||
state: 'all',
|
||||
sort: 'updated',
|
||||
direction: 'desc',
|
||||
per_page: 50
|
||||
});
|
||||
|
||||
for (const pr of prs) {
|
||||
core.info(`Checking PR #${pr.number}: ${pr.title}`);
|
||||
|
||||
const customContext = {
|
||||
...context,
|
||||
issue: { number: pr.number }
|
||||
};
|
||||
|
||||
try {
|
||||
await cleanup({ github, context: customContext, core });
|
||||
} catch (error) {
|
||||
core.warning(`Failed to cleanup PR #${pr.number}: ${error.message}`);
|
||||
}
|
||||
}
|
||||
Loading…
Reference in a new issue