spacehub

surshar / claude-code

Claude Code is an agentic coding tool that lives in your terminal, understands your codebase, and helps you code faster by executing routine tasks, explaining complex code, and handling git workflows - all through natural language commands.

Built by Anthropic · github.com/anthropics

claude-code / scripts / backfill-duplicate-comments.ts
198 lines · 5.6 KBRaw
1#!/usr/bin/env bun
2
3declare global {
4 var process: {
5 env: Record<string, string | undefined>;
6 };
7}
8
9interface GitHubIssue {
10 number: number;
11 title: string;
12 state: string;
13 state_reason?: string;
14 user: { id: number };
15 created_at: string;
16 closed_at?: string;
17}
18
19interface GitHubComment {
20 id: number;
21 body: string;
22 created_at: string;
23 user: { type: string; id: number };
24}
25
26async function githubRequest<T>(endpoint: string, token: string, method: string = 'GET', body?: any): Promise<T> {
27 const response = await fetch(`https://api.github.com${endpoint}`, {
28 method,
29 headers: {
30 Authorization: `Bearer ${token}`,
31 Accept: "application/vnd.github.v3+json",
32 "User-Agent": "backfill-duplicate-comments-script",
33 ...(body && { "Content-Type": "application/json" }),
34 },
35 ...(body && { body: JSON.stringify(body) }),
36 });
37
38 if (!response.ok) {
39 throw new Error(
40 `GitHub API request failed: ${response.status} ${response.statusText}`
41 );
42 }
43
44 return response.json();
45}
46
47async function triggerDedupeWorkflow(
48 owner: string,
49 repo: string,
50 issueNumber: number,
51 token: string,
52 dryRun: boolean = true
53): Promise<void> {
54 if (dryRun) {
55 console.log(`[DRY RUN] Would trigger dedupe workflow for issue #${issueNumber}`);
56 return;
57 }
58
59 await githubRequest(
60 `/repos/${owner}/${repo}/actions/workflows/claude-dedupe-issues.yml/dispatches`,
61 token,
62 'POST',
63 {
64 ref: 'main',
65 inputs: {
66 issue_number: issueNumber.toString()
67 }
68 }
69 );
70}
71
72async function backfillDuplicateComments(): Promise<void> {
73 console.log("[DEBUG] Starting backfill duplicate comments script");
74
75 const token = process.env.GITHUB_TOKEN;
76 if (!token) {
77 throw new Error(`GITHUB_TOKEN environment variable is required
78
79Usage:
80 GITHUB_TOKEN=your_token bun run scripts/backfill-duplicate-comments.ts
81
82Environment Variables:
83 GITHUB_TOKEN - GitHub personal access token with repo and actions permissions (required)
84 DRY_RUN - Set to "false" to actually trigger workflows (default: true for safety)
85 DAYS_BACK - How many days back to look for old issues (default: 90)`);
86 }
87 console.log("[DEBUG] GitHub token found");
88
89 const owner = "anthropics";
90 const repo = "claude-code";
91 const dryRun = process.env.DRY_RUN !== "false";
92 const daysBack = parseInt(process.env.DAYS_BACK || "90", 10);
93
94 console.log(`[DEBUG] Repository: ${owner}/${repo}`);
95 console.log(`[DEBUG] Dry run mode: ${dryRun}`);
96 console.log(`[DEBUG] Looking back ${daysBack} days`);
97
98 const cutoffDate = new Date();
99 cutoffDate.setDate(cutoffDate.getDate() - daysBack);
100
101 console.log(`[DEBUG] Fetching issues created since ${cutoffDate.toISOString()}...`);
102 const allIssues: GitHubIssue[] = [];
103 let page = 1;
104 const perPage = 100;
105
106 while (true) {
107 const pageIssues: GitHubIssue[] = await githubRequest(
108 `/repos/${owner}/${repo}/issues?state=all&per_page=${perPage}&page=${page}&since=${cutoffDate.toISOString()}`,
109 token
110 );
111
112 if (pageIssues.length === 0) break;
113
114 allIssues.push(...pageIssues);
115 page++;
116
117 // Safety limit to avoid infinite loops
118 if (page > 100) {
119 console.log("[DEBUG] Reached page limit, stopping pagination");
120 break;
121 }
122 }
123
124 console.log(`[DEBUG] Found ${allIssues.length} issues from the last ${daysBack} days`);
125
126 let processedCount = 0;
127 let candidateCount = 0;
128 let triggeredCount = 0;
129
130 for (const issue of allIssues) {
131 processedCount++;
132 console.log(
133 `[DEBUG] Processing issue #${issue.number} (${processedCount}/${allIssues.length}): ${issue.title}`
134 );
135
136 console.log(`[DEBUG] Fetching comments for issue #${issue.number}...`);
137 const comments: GitHubComment[] = await githubRequest(
138 `/repos/${owner}/${repo}/issues/${issue.number}/comments`,
139 token
140 );
141 console.log(
142 `[DEBUG] Issue #${issue.number} has ${comments.length} comments`
143 );
144
145 // Look for existing duplicate detection comments (from the dedupe bot)
146 const dupeDetectionComments = comments.filter(
147 (comment) =>
148 comment.body.includes("Found") &&
149 comment.body.includes("possible duplicate") &&
150 comment.user.type === "Bot"
151 );
152
153 console.log(
154 `[DEBUG] Issue #${issue.number} has ${dupeDetectionComments.length} duplicate detection comments`
155 );
156
157 // Skip if there's already a duplicate detection comment
158 if (dupeDetectionComments.length > 0) {
159 console.log(
160 `[DEBUG] Issue #${issue.number} already has duplicate detection comment, skipping`
161 );
162 continue;
163 }
164
165 candidateCount++;
166 const issueUrl = `https://github.com/${owner}/${repo}/issues/${issue.number}`;
167
168 try {
169 console.log(
170 `[INFO] ${dryRun ? '[DRY RUN] ' : ''}Triggering dedupe workflow for issue #${issue.number}: ${issueUrl}`
171 );
172 await triggerDedupeWorkflow(owner, repo, issue.number, token, dryRun);
173
174 if (!dryRun) {
175 console.log(
176 `[SUCCESS] Successfully triggered dedupe workflow for issue #${issue.number}`
177 );
178 }
179 triggeredCount++;
180 } catch (error) {
181 console.error(
182 `[ERROR] Failed to trigger workflow for issue #${issue.number}: ${error}`
183 );
184 }
185
186 // Add a delay between workflow triggers to avoid overwhelming the system
187 await new Promise(resolve => setTimeout(resolve, 1000));
188 }
189
190 console.log(
191 `[DEBUG] Script completed. Processed ${processedCount} issues, found ${candidateCount} candidates without duplicate comments, ${dryRun ? 'would trigger' : 'triggered'} ${triggeredCount} workflows`
192 );
193}
194
195backfillDuplicateComments().catch(console.error);
196
197// Make it a module
198export {};