mirror of
https://github.com/anthropics/claude-code.git
synced 2026-04-20 10:52:41 +00:00
Compare commits
1 Commits
boris/iuni
...
ashwin/upd
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
ead501a4a9 |
2
.github/workflows/auto-close-duplicates.yml
vendored
2
.github/workflows/auto-close-duplicates.yml
vendored
@@ -11,7 +11,7 @@ jobs:
|
||||
timeout-minutes: 10
|
||||
permissions:
|
||||
contents: read
|
||||
issues: write
|
||||
issues: read
|
||||
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
|
||||
112
.github/workflows/log-issue-events.yml
vendored
112
.github/workflows/log-issue-events.yml
vendored
@@ -2,11 +2,10 @@ name: Log GitHub Issue Events
|
||||
|
||||
on:
|
||||
issues:
|
||||
types: [opened, closed]
|
||||
types: [opened]
|
||||
|
||||
jobs:
|
||||
log-issue-created:
|
||||
if: github.event.action == 'opened'
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 5
|
||||
permissions:
|
||||
@@ -20,7 +19,7 @@ jobs:
|
||||
run: |
|
||||
ISSUE_NUMBER=${{ github.event.issue.number }}
|
||||
REPO=${{ github.repository }}
|
||||
ISSUE_TITLE=$(echo '${{ github.event.issue.title }}' | sed "s/'/'\\\\''/g")
|
||||
ISSUE_TITLE="${{ github.event.issue.title }}"
|
||||
AUTHOR="${{ github.event.issue.user.login }}"
|
||||
CREATED_AT="${{ github.event.issue.created_at }}"
|
||||
|
||||
@@ -66,111 +65,4 @@ jobs:
|
||||
echo "Successfully logged issue creation for issue #${ISSUE_NUMBER}"
|
||||
else
|
||||
echo "Failed to log issue creation for issue #${ISSUE_NUMBER}. HTTP ${HTTP_CODE}: ${BODY}"
|
||||
fi
|
||||
|
||||
log-issue-closed:
|
||||
if: github.event.action == 'closed'
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 5
|
||||
permissions:
|
||||
contents: read
|
||||
issues: read
|
||||
|
||||
steps:
|
||||
- name: Log issue closure to Statsig
|
||||
env:
|
||||
STATSIG_API_KEY: ${{ secrets.STATSIG_API_KEY }}
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
run: |
|
||||
ISSUE_NUMBER=${{ github.event.issue.number }}
|
||||
REPO=${{ github.repository }}
|
||||
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 }}"
|
||||
|
||||
if [ -z "$STATSIG_API_KEY" ]; then
|
||||
echo "STATSIG_API_KEY not found, skipping Statsig logging"
|
||||
exit 0
|
||||
fi
|
||||
|
||||
# Get additional issue data via GitHub API
|
||||
echo "Fetching additional issue data for #${ISSUE_NUMBER}"
|
||||
ISSUE_DATA=$(curl -s -H "Authorization: token ${GITHUB_TOKEN}" \
|
||||
-H "Accept: application/vnd.github.v3+json" \
|
||||
"https://api.github.com/repos/${REPO}/issues/${ISSUE_NUMBER}")
|
||||
|
||||
COMMENTS_COUNT=$(echo "$ISSUE_DATA" | jq -r '.comments')
|
||||
|
||||
# Get reactions data
|
||||
REACTIONS_DATA=$(curl -s -H "Authorization: token ${GITHUB_TOKEN}" \
|
||||
-H "Accept: application/vnd.github.v3+json" \
|
||||
"https://api.github.com/repos/${REPO}/issues/${ISSUE_NUMBER}/reactions")
|
||||
|
||||
REACTIONS_COUNT=$(echo "$REACTIONS_DATA" | jq '. | length')
|
||||
|
||||
# Check if issue was closed automatically (by checking if closed_by is a bot)
|
||||
CLOSED_AUTOMATICALLY="false"
|
||||
if [[ "$CLOSED_BY" == *"[bot]"* ]]; then
|
||||
CLOSED_AUTOMATICALLY="true"
|
||||
fi
|
||||
|
||||
# Check if closed as duplicate by state_reason
|
||||
CLOSED_AS_DUPLICATE="false"
|
||||
if [ "$STATE_REASON" = "duplicate" ]; then
|
||||
CLOSED_AS_DUPLICATE="true"
|
||||
fi
|
||||
|
||||
# Prepare the event payload
|
||||
EVENT_PAYLOAD=$(jq -n \
|
||||
--arg issue_number "$ISSUE_NUMBER" \
|
||||
--arg repo "$REPO" \
|
||||
--arg title "$ISSUE_TITLE" \
|
||||
--arg closed_by "$CLOSED_BY" \
|
||||
--arg closed_at "$CLOSED_AT" \
|
||||
--arg state_reason "$STATE_REASON" \
|
||||
--arg comments_count "$COMMENTS_COUNT" \
|
||||
--arg reactions_count "$REACTIONS_COUNT" \
|
||||
--arg closed_automatically "$CLOSED_AUTOMATICALLY" \
|
||||
--arg closed_as_duplicate "$CLOSED_AS_DUPLICATE" \
|
||||
'{
|
||||
events: [{
|
||||
eventName: "github_issue_closed",
|
||||
value: 1,
|
||||
metadata: {
|
||||
repository: $repo,
|
||||
issue_number: ($issue_number | tonumber),
|
||||
issue_title: $title,
|
||||
closed_by: $closed_by,
|
||||
closed_at: $closed_at,
|
||||
state_reason: $state_reason,
|
||||
comments_count: ($comments_count | tonumber),
|
||||
reactions_count: ($reactions_count | tonumber),
|
||||
closed_automatically: ($closed_automatically | test("true")),
|
||||
closed_as_duplicate: ($closed_as_duplicate | test("true"))
|
||||
},
|
||||
time: (now | floor | tostring)
|
||||
}]
|
||||
}')
|
||||
|
||||
# Send to Statsig API
|
||||
echo "Logging issue closure to Statsig for issue #${ISSUE_NUMBER}"
|
||||
|
||||
RESPONSE=$(curl -s -w "\n%{http_code}" -X POST https://events.statsigapi.net/v1/log_event \
|
||||
-H "Content-Type: application/json" \
|
||||
-H "STATSIG-API-KEY: ${STATSIG_API_KEY}" \
|
||||
-d "$EVENT_PAYLOAD")
|
||||
|
||||
HTTP_CODE=$(echo "$RESPONSE" | tail -n1)
|
||||
BODY=$(echo "$RESPONSE" | head -n-1)
|
||||
|
||||
if [ "$HTTP_CODE" -eq 200 ] || [ "$HTTP_CODE" -eq 202 ]; then
|
||||
echo "Successfully logged issue closure for issue #${ISSUE_NUMBER}"
|
||||
echo "Closed by: $CLOSED_BY"
|
||||
echo "Comments: $COMMENTS_COUNT"
|
||||
echo "Reactions: $REACTIONS_COUNT"
|
||||
echo "Closed automatically: $CLOSED_AUTOMATICALLY"
|
||||
echo "Closed as duplicate: $CLOSED_AS_DUPLICATE"
|
||||
else
|
||||
echo "Failed to log issue closure for issue #${ISSUE_NUMBER}. HTTP ${HTTP_CODE}: ${BODY}"
|
||||
fi
|
||||
28
CHANGELOG.md
28
CHANGELOG.md
@@ -1,33 +1,5 @@
|
||||
# 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.)
|
||||
|
||||
@@ -47,21 +47,45 @@ async function githubRequest<T>(endpoint: string, token: string, method: string
|
||||
}
|
||||
|
||||
function extractDuplicateIssueNumber(commentBody: string): number | 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;
|
||||
const match = commentBody.match(/#(\d+)/);
|
||||
return match ? parseInt(match[1], 10) : 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,
|
||||
@@ -76,8 +100,7 @@ async function closeIssueAsDuplicate(
|
||||
'PATCH',
|
||||
{
|
||||
state: 'closed',
|
||||
state_reason: 'duplicate',
|
||||
labels: ['duplicate']
|
||||
state_reason: 'not_planned'
|
||||
}
|
||||
);
|
||||
|
||||
@@ -94,6 +117,13 @@ 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> {
|
||||
|
||||
@@ -82,61 +82,46 @@ 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)
|
||||
MAX_ISSUE_NUMBER - Only process issues with numbers less than this value (default: 4050)`);
|
||||
DAYS_BACK - How many days back to look for old issues (default: 90)`);
|
||||
}
|
||||
console.log("[DEBUG] GitHub token found");
|
||||
|
||||
const owner = "anthropics";
|
||||
const repo = "claude-code";
|
||||
const dryRun = process.env.DRY_RUN !== "false";
|
||||
const maxIssueNumber = parseInt(process.env.MAX_ISSUE_NUMBER || "4050", 10);
|
||||
const minIssueNumber = parseInt(process.env.MIN_ISSUE_NUMBER || "1", 10);
|
||||
const daysBack = parseInt(process.env.DAYS_BACK || "90", 10);
|
||||
|
||||
console.log(`[DEBUG] Repository: ${owner}/${repo}`);
|
||||
console.log(`[DEBUG] Dry run mode: ${dryRun}`);
|
||||
console.log(`[DEBUG] Looking at issues between #${minIssueNumber} and #${maxIssueNumber}`);
|
||||
console.log(`[DEBUG] Looking back ${daysBack} days`);
|
||||
|
||||
console.log(`[DEBUG] Fetching issues between #${minIssueNumber} and #${maxIssueNumber}...`);
|
||||
const cutoffDate = new Date();
|
||||
cutoffDate.setDate(cutoffDate.getDate() - daysBack);
|
||||
|
||||
console.log(`[DEBUG] Fetching issues created since ${cutoffDate.toISOString()}...`);
|
||||
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}&sort=created&direction=desc`,
|
||||
`/repos/${owner}/${repo}/issues?state=all&per_page=${perPage}&page=${page}&since=${cutoffDate.toISOString()}`,
|
||||
token
|
||||
);
|
||||
|
||||
if (pageIssues.length === 0) break;
|
||||
|
||||
// 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...`);
|
||||
}
|
||||
|
||||
allIssues.push(...pageIssues);
|
||||
page++;
|
||||
|
||||
// Safety limit to avoid infinite loops
|
||||
if (page > 200) {
|
||||
if (page > 100) {
|
||||
console.log("[DEBUG] Reached page limit, stopping pagination");
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
console.log(`[DEBUG] Found ${allIssues.length} issues between #${minIssueNumber} and #${maxIssueNumber}`);
|
||||
console.log(`[DEBUG] Found ${allIssues.length} issues from the last ${daysBack} days`);
|
||||
|
||||
let processedCount = 0;
|
||||
let candidateCount = 0;
|
||||
|
||||
Reference in New Issue
Block a user