Compare commits

...

15 Commits

Author SHA1 Message Date
Boris Cherny
20ba9a34a5 feat: update dedupe backfill script to filter by issue number
Replace date-based filtering with issue number filtering to only process issues older than #4050. This provides more precise control over which issues are processed for duplicate detection backfill.

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-08-18 09:54:40 -07:00
Boris Cherny
fc8c10995f Merge pull request #5858 from anthropics/boris/vreg
fix: update auto-close-duplicates workflow permissions to write
2025-08-15 10:57:02 -07:00
Boris Cherny
01fb7af5b3 fix: update auto-close-duplicates workflow permissions to write
Change issues permission from read to write to fix 403 Forbidden
errors when attempting to close duplicate issues.

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-08-15 10:55:50 -07:00
Boris Cherny
afb0fc9156 Merge pull request #5802 from anthropics/boris/jymy
fix: improve duplicate issue number extraction in auto-close script
2025-08-14 16:38:49 -07:00
Boris Cherny
370a97d939 fix: improve duplicate issue number extraction in auto-close script
The extractDuplicateIssueNumber function now handles both #123 format
and full GitHub issue URLs like https://github.com/owner/repo/issues/123.
This fixes the "could not extract duplicate issue number from comment"
errors that were occurring when the script encountered URL-formatted
issue references in duplicate detection comments.

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-08-14 16:37:46 -07:00
GitHub Actions
f54569efd2 chore: Update CHANGELOG.md 2025-08-14 20:13:16 +00:00
GitHub Actions
d8cf5a874c chore: Update CHANGELOG.md 2025-08-14 16:59:55 +00:00
GitHub Actions
e499db6e9e chore: Update CHANGELOG.md 2025-08-11 23:43:35 +00:00
Boris Cherny
5300e12135 Merge pull request #5569 from anthropics/boris/limc
Consolidate GitHub issue closure events
2025-08-11 16:07:18 -07:00
Boris Cherny
4a04589002 Use proper 'duplicate' state_reason for issue closures
- Update auto-close script to use state_reason: 'duplicate' instead of 'not_planned'
- Simplify workflow detection logic to only check for duplicate state_reason
- Remove fallback logic for backward compatibility - use modern GitHub API

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-08-11 15:44:08 -07:00
Boris Cherny
04cace9ec0 Consolidate GitHub issue closure events to prevent duplicates
- Remove duplicate Statsig logging from auto-close-duplicates.ts
- GitHub workflow now handles all issue closures uniformly
- Add 'duplicate' label to ensure proper detection by workflow
- Prevents double-logging when script closes issues

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-08-11 15:33:31 -07:00
Boris Cherny
2dbf1e97a0 Merge pull request #5564 from anthropics/boris/lptz
Fix GitHub Actions workflow to properly escape issue titles
2025-08-11 13:49:50 -07:00
Boris Cherny
0662600e93 Fix GitHub Actions workflow to properly escape issue titles
Prevents shell execution of backticks in issue titles by using single quotes and sed escaping.

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-08-11 13:38:40 -07:00
Boris Cherny
27d2c6fdcf Merge pull request #5562 from anthropics/boris/zivm
Add GitHub workflow logging for issue closure events
2025-08-11 13:17:26 -07:00
GitHub Actions
dd53f86325 chore: Update CHANGELOG.md 2025-08-11 20:15:53 +00:00
5 changed files with 75 additions and 66 deletions

View File

@@ -11,7 +11,7 @@ jobs:
timeout-minutes: 10
permissions:
contents: read
issues: read
issues: write
steps:
- name: Checkout repository

View File

@@ -20,7 +20,7 @@ jobs:
run: |
ISSUE_NUMBER=${{ github.event.issue.number }}
REPO=${{ github.repository }}
ISSUE_TITLE="${{ github.event.issue.title }}"
ISSUE_TITLE=$(echo '${{ github.event.issue.title }}' | sed "s/'/'\\\\''/g")
AUTHOR="${{ github.event.issue.user.login }}"
CREATED_AT="${{ github.event.issue.created_at }}"
@@ -84,7 +84,7 @@ jobs:
run: |
ISSUE_NUMBER=${{ github.event.issue.number }}
REPO=${{ github.repository }}
ISSUE_TITLE="${{ github.event.issue.title }}"
ISSUE_TITLE=$(echo '${{ github.event.issue.title }}' | sed "s/'/'\\\\''/g")
CLOSED_BY="${{ github.event.issue.closed_by.login }}"
CLOSED_AT="${{ github.event.issue.closed_at }}"
STATE_REASON="${{ github.event.issue.state_reason }}"
@@ -115,14 +115,10 @@ jobs:
CLOSED_AUTOMATICALLY="true"
fi
# Check if closed as duplicate by looking for duplicate label or state_reason
# Check if closed as duplicate by state_reason
CLOSED_AS_DUPLICATE="false"
if [ "$STATE_REASON" = "not_planned" ]; then
# Check if issue has duplicate label
LABELS=$(echo "$ISSUE_DATA" | jq -r '.labels[] | select(.name | test("duplicate"; "i")) | .name')
if [ -n "$LABELS" ]; then
CLOSED_AS_DUPLICATE="true"
fi
if [ "$STATE_REASON" = "duplicate" ]; then
CLOSED_AS_DUPLICATE="true"
fi
# Prepare the event payload

View File

@@ -1,5 +1,33 @@
# Changelog
## 1.0.81
- Released output styles, including new built-in educational output styles "Explanatory" and "Learning". Docs: https://docs.anthropic.com/en/docs/claude-code/output-styles
- Agents: Fix custom agent loading when agent files are unparsable
## 1.0.80
- UI improvements: Fix text contrast for custom subagent colors and spinner rendering issues
## 1.0.77
- Bash tool: Fix heredoc and multiline string escaping, improve stderr redirection handling
- SDK: Add session support and permission denial tracking
- Fix token limit errors in conversation summarization
- Opus Plan Mode: New setting in `/model` to run Opus only in plan mode, Sonnet otherwise
## 1.0.73
- MCP: Support multiple config files with `--mcp-config file1.json file2.json`
- MCP: Press Esc to cancel OAuth authentication flows
- Bash: Improved command validation and reduced false security warnings
- UI: Enhanced spinner animations and status line visual hierarchy
- Linux: Added support for Alpine and musl-based distributions (requires separate ripgrep installation)
## 1.0.72
- Ask permissions: have Claude Code always ask for confirmation to use specific tools with /permissions
## 1.0.71
- Background commands: (Ctrl-b) to run any Bash command in the background so Claude can keep working (great for dev servers, tailing logs, etc.)

View File

@@ -47,45 +47,21 @@ async function githubRequest<T>(endpoint: string, token: string, method: string
}
function extractDuplicateIssueNumber(commentBody: string): number | null {
const match = commentBody.match(/#(\d+)/);
return match ? parseInt(match[1], 10) : null;
// Try to match #123 format first
let match = commentBody.match(/#(\d+)/);
if (match) {
return parseInt(match[1], 10);
}
// Try to match GitHub issue URL format: https://github.com/owner/repo/issues/123
match = commentBody.match(/github\.com\/[^\/]+\/[^\/]+\/issues\/(\d+)/);
if (match) {
return parseInt(match[1], 10);
}
return null;
}
async function logStatsigEvent(eventName: string, value: number, metadata: Record<string, any>): Promise<void> {
const statsigApiKey = process.env.STATSIG_API_KEY;
if (!statsigApiKey) {
console.log("[DEBUG] STATSIG_API_KEY not found, skipping Statsig logging");
return;
}
const eventPayload = {
events: [{
eventName,
value,
metadata,
time: Math.floor(Date.now()).toString()
}]
};
try {
const response = await fetch('https://events.statsigapi.net/v1/log_event', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'STATSIG-API-KEY': statsigApiKey
},
body: JSON.stringify(eventPayload)
});
if (response.ok) {
console.log(`[DEBUG] Successfully logged Statsig event: ${eventName}`);
} else {
console.log(`[DEBUG] Failed to log Statsig event: ${response.status} ${response.statusText}`);
}
} catch (error) {
console.log(`[DEBUG] Error logging to Statsig: ${error}`);
}
}
async function closeIssueAsDuplicate(
owner: string,
@@ -100,7 +76,8 @@ async function closeIssueAsDuplicate(
'PATCH',
{
state: 'closed',
state_reason: 'not_planned'
state_reason: 'duplicate',
labels: ['duplicate']
}
);
@@ -117,13 +94,6 @@ If this is incorrect, please re-open this issue or create a new one.
}
);
// Log to Statsig
await logStatsigEvent('github_issue_closed_as_duplicate', 1, {
repository: `${owner}/${repo}`,
issue_number: issueNumber,
duplicate_of_issue: duplicateOfNumber,
closed_by: 'auto-close-script'
});
}
async function autoCloseDuplicates(): Promise<void> {

View File

@@ -82,46 +82,61 @@ Usage:
Environment Variables:
GITHUB_TOKEN - GitHub personal access token with repo and actions permissions (required)
DRY_RUN - Set to "false" to actually trigger workflows (default: true for safety)
DAYS_BACK - How many days back to look for old issues (default: 90)`);
MAX_ISSUE_NUMBER - Only process issues with numbers less than this value (default: 4050)`);
}
console.log("[DEBUG] GitHub token found");
const owner = "anthropics";
const repo = "claude-code";
const dryRun = process.env.DRY_RUN !== "false";
const daysBack = parseInt(process.env.DAYS_BACK || "90", 10);
const maxIssueNumber = parseInt(process.env.MAX_ISSUE_NUMBER || "4050", 10);
const minIssueNumber = parseInt(process.env.MIN_ISSUE_NUMBER || "1", 10);
console.log(`[DEBUG] Repository: ${owner}/${repo}`);
console.log(`[DEBUG] Dry run mode: ${dryRun}`);
console.log(`[DEBUG] Looking back ${daysBack} days`);
console.log(`[DEBUG] Looking at issues between #${minIssueNumber} and #${maxIssueNumber}`);
const cutoffDate = new Date();
cutoffDate.setDate(cutoffDate.getDate() - daysBack);
console.log(`[DEBUG] Fetching issues created since ${cutoffDate.toISOString()}...`);
console.log(`[DEBUG] Fetching issues between #${minIssueNumber} and #${maxIssueNumber}...`);
const allIssues: GitHubIssue[] = [];
let page = 1;
const perPage = 100;
while (true) {
const pageIssues: GitHubIssue[] = await githubRequest(
`/repos/${owner}/${repo}/issues?state=all&per_page=${perPage}&page=${page}&since=${cutoffDate.toISOString()}`,
`/repos/${owner}/${repo}/issues?state=all&per_page=${perPage}&page=${page}&sort=created&direction=desc`,
token
);
if (pageIssues.length === 0) break;
allIssues.push(...pageIssues);
// Filter to only include issues within the specified range
const filteredIssues = pageIssues.filter(issue =>
issue.number >= minIssueNumber && issue.number < maxIssueNumber
);
allIssues.push(...filteredIssues);
// If the oldest issue in this page is still above our minimum, we need to continue
// but if the oldest issue is below our minimum, we can stop
const oldestIssueInPage = pageIssues[pageIssues.length - 1];
if (oldestIssueInPage && oldestIssueInPage.number >= maxIssueNumber) {
console.log(`[DEBUG] Oldest issue in page #${page} is #${oldestIssueInPage.number}, continuing...`);
} else if (oldestIssueInPage && oldestIssueInPage.number < minIssueNumber) {
console.log(`[DEBUG] Oldest issue in page #${page} is #${oldestIssueInPage.number}, below minimum, stopping`);
break;
} else if (filteredIssues.length === 0 && pageIssues.length > 0) {
console.log(`[DEBUG] No issues in page #${page} are in range #${minIssueNumber}-#${maxIssueNumber}, continuing...`);
}
page++;
// Safety limit to avoid infinite loops
if (page > 100) {
if (page > 200) {
console.log("[DEBUG] Reached page limit, stopping pagination");
break;
}
}
console.log(`[DEBUG] Found ${allIssues.length} issues from the last ${daysBack} days`);
console.log(`[DEBUG] Found ${allIssues.length} issues between #${minIssueNumber} and #${maxIssueNumber}`);
let processedCount = 0;
let candidateCount = 0;