Compare commits

...

5 Commits

Author SHA1 Message Date
Boris Cherny
5af0b38a92 Add Statsig event logging to GitHub issue workflows
- Log events when issues are closed as duplicates in auto-close script
- Log events when duplicate comments are added via dedupe workflow
- Log events when new issues are created
- Follow existing pattern from code review reactions workflow

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

Co-Authored-By: Claude <noreply@anthropic.com>
2025-08-08 18:26:48 -07:00
Boris Cherny
22946869b2 Merge pull request #5423 from anthropics/boris/uqbo
Rename auto-close duplicate issues workflow to remove "dry run"
2025-08-08 17:28:36 -07:00
Boris Cherny
1579216fc7 Enable auto-close duplicate issues workflow
Remove DRY RUN from workflow name and description to activate the automatic closing of duplicate issues after the testing period.

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

Co-Authored-By: Claude <noreply@anthropic.com>
2025-08-08 14:03:43 -07:00
Ashwin Bhat
f0042afb3b Replace lock-threads action with GitHub Script (#5330)
Replaces the dessant/lock-threads action with a direct GitHub Script
implementation to avoid the deprecated search/issues API endpoint warning.
The new implementation:
- Uses github.rest.issues.listForRepo() instead of the deprecated search API
- Maintains the same 7-day inactivity threshold
- Adds the same comment before locking
- Uses 'resolved' as the lock reason
- Handles pagination properly for large repositories

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

Co-authored-by: Claude <noreply@anthropic.com>
2025-08-08 13:02:20 -07:00
Boris Cherny
6059607354 Merge pull request #5420 from anthropics/boris/cmyy
Fix workflow failure by adding workflow_dispatch trigger
2025-08-08 12:57:16 -07:00
5 changed files with 241 additions and 14 deletions

View File

@@ -1,5 +1,5 @@
name: Auto-close duplicate issues (DRY RUN)
description: Dry run - logs issues that would be auto-closed as duplicates after 3 days if no response
name: Auto-close duplicate issues
description: Auto-closes issues that are duplicates of existing issues
on:
schedule:
- cron: "0 9 * * *"
@@ -16,15 +16,16 @@ jobs:
steps:
- name: Checkout repository
uses: actions/checkout@v4
- name: Setup Bun
uses: oven-sh/setup-bun@v2
with:
bun-version: latest
- name: Auto-close duplicate issues
run: bun run scripts/auto-close-duplicates.ts
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
GITHUB_REPOSITORY_OWNER: ${{ github.repository_owner }}
GITHUB_REPOSITORY_NAME: ${{ github.event.repository.name }}
STATSIG_API_KEY: ${{ secrets.STATSIG_API_KEY }}

View File

@@ -29,3 +29,52 @@ jobs:
anthropic_api_key: ${{ secrets.ANTHROPIC_API_KEY }}
claude_env: |
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
- name: Log duplicate comment event to Statsig
if: always()
env:
STATSIG_API_KEY: ${{ secrets.STATSIG_API_KEY }}
run: |
ISSUE_NUMBER=${{ github.event.issue.number || inputs.issue_number }}
REPO=${{ github.repository }}
if [ -z "$STATSIG_API_KEY" ]; then
echo "STATSIG_API_KEY not found, skipping Statsig logging"
exit 0
fi
# Prepare the event payload
EVENT_PAYLOAD=$(jq -n \
--arg issue_number "$ISSUE_NUMBER" \
--arg repo "$REPO" \
--arg triggered_by "${{ github.event_name }}" \
'{
events: [{
eventName: "github_duplicate_comment_added",
value: 1,
metadata: {
repository: $repo,
issue_number: ($issue_number | tonumber),
triggered_by: $triggered_by,
workflow_run_id: "${{ github.run_id }}"
},
time: (now | floor | tostring)
}]
}')
# Send to Statsig API
echo "Logging duplicate comment event 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 duplicate comment event for issue #${ISSUE_NUMBER}"
else
echo "Failed to log duplicate comment event for issue #${ISSUE_NUMBER}. HTTP ${HTTP_CODE}: ${BODY}"
fi

View File

@@ -13,16 +13,81 @@ concurrency:
group: lock-threads
jobs:
lock-threads:
lock-closed-issues:
runs-on: ubuntu-latest
timeout-minutes: 10
steps:
- uses: dessant/lock-threads@1bf7ec25051fe7c00bdd17e6a7cf3d7bfb7dc771 # v5.0.1
- name: Lock closed issues after 7 days of inactivity
uses: actions/github-script@v7
with:
issue-inactive-days: "7"
process-only: "issues"
log-output: true
issue-comment: >
This issue has been automatically locked since it was
closed and has not had any activity for 7 days.
If you're experiencing a similar issue, please file a new issue
and reference this one if it's relevant.
script: |
const sevenDaysAgo = new Date();
sevenDaysAgo.setDate(sevenDaysAgo.getDate() - 7);
const lockComment = `This issue has been automatically locked since it was closed and has not had any activity for 7 days. If you're experiencing a similar issue, please file a new issue and reference this one if it's relevant.`;
let page = 1;
let hasMore = true;
let totalLocked = 0;
while (hasMore) {
// Get closed issues (pagination)
const { data: issues } = await github.rest.issues.listForRepo({
owner: context.repo.owner,
repo: context.repo.repo,
state: 'closed',
sort: 'updated',
direction: 'asc',
per_page: 100,
page: page
});
if (issues.length === 0) {
hasMore = false;
break;
}
for (const issue of issues) {
// Skip if already locked
if (issue.locked) continue;
// Skip pull requests
if (issue.pull_request) continue;
// Check if updated more than 7 days ago
const updatedAt = new Date(issue.updated_at);
if (updatedAt > sevenDaysAgo) {
// Since issues are sorted by updated_at ascending,
// once we hit a recent issue, all remaining will be recent too
hasMore = false;
break;
}
try {
// Add comment before locking
await github.rest.issues.createComment({
owner: context.repo.owner,
repo: context.repo.repo,
issue_number: issue.number,
body: lockComment
});
// Lock the issue
await github.rest.issues.lock({
owner: context.repo.owner,
repo: context.repo.repo,
issue_number: issue.number,
lock_reason: 'resolved'
});
totalLocked++;
console.log(`Locked issue #${issue.number}: ${issue.title}`);
} catch (error) {
console.error(`Failed to lock issue #${issue.number}: ${error.message}`);
}
}
page++;
}
console.log(`Total issues locked: ${totalLocked}`);

68
.github/workflows/log-issue-events.yml vendored Normal file
View File

@@ -0,0 +1,68 @@
name: Log GitHub Issue Events
on:
issues:
types: [opened]
jobs:
log-issue-created:
runs-on: ubuntu-latest
timeout-minutes: 5
permissions:
contents: read
issues: read
steps:
- name: Log issue creation to Statsig
env:
STATSIG_API_KEY: ${{ secrets.STATSIG_API_KEY }}
run: |
ISSUE_NUMBER=${{ github.event.issue.number }}
REPO=${{ github.repository }}
ISSUE_TITLE="${{ github.event.issue.title }}"
AUTHOR="${{ github.event.issue.user.login }}"
CREATED_AT="${{ github.event.issue.created_at }}"
if [ -z "$STATSIG_API_KEY" ]; then
echo "STATSIG_API_KEY not found, skipping Statsig logging"
exit 0
fi
# Prepare the event payload
EVENT_PAYLOAD=$(jq -n \
--arg issue_number "$ISSUE_NUMBER" \
--arg repo "$REPO" \
--arg title "$ISSUE_TITLE" \
--arg author "$AUTHOR" \
--arg created_at "$CREATED_AT" \
'{
events: [{
eventName: "github_issue_created",
value: 1,
metadata: {
repository: $repo,
issue_number: ($issue_number | tonumber),
issue_title: $title,
issue_author: $author,
created_at: $created_at
},
time: (now | floor | tostring)
}]
}')
# Send to Statsig API
echo "Logging issue creation 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 creation for issue #${ISSUE_NUMBER}"
else
echo "Failed to log issue creation for issue #${ISSUE_NUMBER}. HTTP ${HTTP_CODE}: ${BODY}"
fi

View File

@@ -51,6 +51,42 @@ function extractDuplicateIssueNumber(commentBody: string): number | null {
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,
repo: string,
@@ -80,6 +116,14 @@ If this is incorrect, please re-open this issue or create a new one.
🤖 Generated with [Claude Code](https://claude.ai/code)`
}
);
// 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> {