mirror of
https://github.com/anthropics/claude-code.git
synced 2026-02-19 04:27:33 -08:00
Compare commits
117 Commits
ashwin/che
...
boris/oauy
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
d820a4dbd7 | ||
|
|
eb0e43457b | ||
|
|
b417bfc532 | ||
|
|
239aeb55ee | ||
|
|
f4e707fdcc | ||
|
|
d2f88820c9 | ||
|
|
a3620cdd0b | ||
|
|
da6d2f715e | ||
|
|
f200ab3c5c | ||
|
|
fa29b8f9c0 | ||
|
|
2558619a83 | ||
|
|
80ceacaa78 | ||
|
|
4e63568abd | ||
|
|
5d0b81ae41 | ||
|
|
b1751f2e86 | ||
|
|
eb48d5e4a8 | ||
|
|
fc8c10995f | ||
|
|
01fb7af5b3 | ||
|
|
afb0fc9156 | ||
|
|
370a97d939 | ||
|
|
f54569efd2 | ||
|
|
d8cf5a874c | ||
|
|
e499db6e9e | ||
|
|
5300e12135 | ||
|
|
4a04589002 | ||
|
|
04cace9ec0 | ||
|
|
2dbf1e97a0 | ||
|
|
0662600e93 | ||
|
|
27d2c6fdcf | ||
|
|
dd53f86325 | ||
|
|
c40c658e1f | ||
|
|
e05411140d | ||
|
|
ce5b9164fa | ||
|
|
5af0b38a92 | ||
|
|
22946869b2 | ||
|
|
1579216fc7 | ||
|
|
f0042afb3b | ||
|
|
6059607354 | ||
|
|
478f63be73 | ||
|
|
a7edbdc9e7 | ||
|
|
399a7dcf2f | ||
|
|
9ced2a3470 | ||
|
|
d7aa61e6f1 | ||
|
|
072856dd5b | ||
|
|
1cc90e9b78 | ||
|
|
483e0e892f | ||
|
|
5248fa06bc | ||
|
|
eb48a37a48 | ||
|
|
fdb7efe6f1 | ||
|
|
7060992aa5 | ||
|
|
b7116c78d8 | ||
|
|
3fd750e3cb | ||
|
|
3262b673c3 | ||
|
|
369bd9b6d7 | ||
|
|
611956def4 | ||
|
|
a9f1fefbe9 | ||
|
|
3d5ef4e8c0 | ||
|
|
d967bbbe30 | ||
|
|
5faa082d6e | ||
|
|
60124df21f | ||
|
|
1aaffa4e6d | ||
|
|
f5b24d5480 | ||
|
|
07e6bec5ff | ||
|
|
cf8c6fdf2d | ||
|
|
d720bf7aba | ||
|
|
dde41f6225 | ||
|
|
78e0785950 | ||
|
|
b3658880e5 | ||
|
|
9be8e07e92 | ||
|
|
c904f0f409 | ||
|
|
6418cacb0b | ||
|
|
4fd2c49e8b | ||
|
|
48ed55b459 | ||
|
|
8b2cbe3f86 | ||
|
|
40251280cc | ||
|
|
8f0379c698 | ||
|
|
10c1ec5391 | ||
|
|
6c7836e02f | ||
|
|
6f6fc43c73 | ||
|
|
944eb4eff6 | ||
|
|
21df74bb49 | ||
|
|
66ce673883 | ||
|
|
07b198b9de | ||
|
|
8ad36c459c | ||
|
|
b48dfb8e87 | ||
|
|
55219b8b4e | ||
|
|
812c27b8b3 | ||
|
|
e0d79c3571 | ||
|
|
c8207b4f68 | ||
|
|
4c056f7a09 | ||
|
|
b328530abd | ||
|
|
486b305708 | ||
|
|
8e23e7d791 | ||
|
|
68d43db2a0 | ||
|
|
9285dfbf2f | ||
|
|
90c26533d1 | ||
|
|
d45bce242d | ||
|
|
f91aed5440 | ||
|
|
54a4ed0f5e | ||
|
|
33e37bd828 | ||
|
|
ff15c6f147 | ||
|
|
0cbe1dcac5 | ||
|
|
a705bca81c | ||
|
|
ecaf0d818a | ||
|
|
397442ddf5 | ||
|
|
5def9264e5 | ||
|
|
0149827a77 | ||
|
|
c93c724eeb | ||
|
|
545d78c331 | ||
|
|
e16c9857ef | ||
|
|
a39ae004aa | ||
|
|
74ba615503 | ||
|
|
3d2166eec9 | ||
|
|
beacb95320 | ||
|
|
46ca39c463 | ||
|
|
a6091b65c0 | ||
|
|
11cfc055af |
19
.claude/commands/commit-push-pr.md
Normal file
19
.claude/commands/commit-push-pr.md
Normal file
@@ -0,0 +1,19 @@
|
||||
---
|
||||
allowed-tools: Bash(git checkout --branch:*), Bash(git add:*), Bash(git status:*), Bash(git push:*), Bash(git commit:*), Bash(gh pr create:*)
|
||||
description: Commit, push, and open a PR
|
||||
---
|
||||
|
||||
## Context
|
||||
|
||||
- Current git status: !`git status`
|
||||
- Current git diff (staged and unstaged changes): !`git diff HEAD`
|
||||
- Current branch: !`git branch --show-current`
|
||||
|
||||
## Your task
|
||||
|
||||
Based on the above changes:
|
||||
1. Create a new branch if on main
|
||||
2. Create a single commit with an appropriate message
|
||||
3. Push the branch to origin
|
||||
4. Create a pull request using `gh pr create`
|
||||
5. You have the capability to call multiple tools in a single response. You MUST do all of the above in a single message. Do not use any other tools or do anything else. Do not send any other text or messages besides these tool calls.
|
||||
38
.claude/commands/dedupe.md
Normal file
38
.claude/commands/dedupe.md
Normal file
@@ -0,0 +1,38 @@
|
||||
---
|
||||
allowed-tools: Bash(gh issue view:*), Bash(gh search:*), Bash(gh issue list:*), Bash(gh api:*), Bash(gh issue comment:*)
|
||||
description: Find duplicate GitHub issues
|
||||
---
|
||||
|
||||
Find up to 3 likely duplicate issues for a given GitHub issue.
|
||||
|
||||
To do this, follow these steps precisely:
|
||||
|
||||
1. Use an agent to check if the Github issue (a) is closed, (b) does not need to be deduped (eg. because it is broad product feedback without a specific solution, or positive feedback), or (c) already has a duplicates comment that you made earlier. If so, do not proceed.
|
||||
2. Use an agent to view a Github issue, and ask the agent to return a summary of the issue
|
||||
3. Then, launch 5 parallel agents to search Github for duplicates of this issue, using diverse keywords and search approaches, using the summary from #1
|
||||
4. Next, feed the results from #1 and #2 into another agent, so that it can filter out false positives, that are likely not actually duplicates of the original issue. If there are no duplicates remaining, do not proceed.
|
||||
5. Finally, comment back on the issue with a list of up to three duplicate issues (or zero, if there are no likely duplicates)
|
||||
|
||||
Notes (be sure to tell this to your agents, too):
|
||||
|
||||
- Use `gh` to interact with Github, rather than web fetch
|
||||
- Do not use other tools, beyond `gh` (eg. don't use other MCP servers, file edit, etc.)
|
||||
- Make a todo list first
|
||||
- For your comment, follow the following format precisely (assuming for this example that you found 3 suspected duplicates):
|
||||
|
||||
---
|
||||
|
||||
Found 3 possible duplicate issues:
|
||||
|
||||
1. <link to issue>
|
||||
2. <link to issue>
|
||||
3. <link to issue>
|
||||
|
||||
This issue will be automatically closed as a duplicate in 3 days.
|
||||
|
||||
- If your issue is a duplicate, please close it and 👍 the existing issue instead
|
||||
- To prevent auto-closure, add a comment or 👎 this comment
|
||||
|
||||
🤖 Generated with [Claude Code](https://claude.ai/code)
|
||||
|
||||
---
|
||||
@@ -3,8 +3,11 @@ FROM node:20
|
||||
ARG TZ
|
||||
ENV TZ="$TZ"
|
||||
|
||||
ARG CLAUDE_CODE_VERSION=latest
|
||||
|
||||
# Install basic development tools and iptables/ipset
|
||||
RUN apt update && apt install -y less \
|
||||
RUN apt-get update && apt-get install -y --no-install-recommends \
|
||||
less \
|
||||
git \
|
||||
procps \
|
||||
sudo \
|
||||
@@ -19,7 +22,10 @@ RUN apt update && apt install -y less \
|
||||
iproute2 \
|
||||
dnsutils \
|
||||
aggregate \
|
||||
jq
|
||||
jq \
|
||||
nano \
|
||||
vim \
|
||||
&& apt-get clean && rm -rf /var/lib/apt/lists/*
|
||||
|
||||
# Ensure default node user has access to /usr/local/share
|
||||
RUN mkdir -p /usr/local/share/npm-global && \
|
||||
@@ -42,10 +48,11 @@ RUN mkdir -p /workspace /home/node/.claude && \
|
||||
|
||||
WORKDIR /workspace
|
||||
|
||||
ARG GIT_DELTA_VERSION=0.18.2
|
||||
RUN ARCH=$(dpkg --print-architecture) && \
|
||||
wget "https://github.com/dandavison/delta/releases/download/0.18.2/git-delta_0.18.2_${ARCH}.deb" && \
|
||||
sudo dpkg -i "git-delta_0.18.2_${ARCH}.deb" && \
|
||||
rm "git-delta_0.18.2_${ARCH}.deb"
|
||||
wget "https://github.com/dandavison/delta/releases/download/${GIT_DELTA_VERSION}/git-delta_${GIT_DELTA_VERSION}_${ARCH}.deb" && \
|
||||
sudo dpkg -i "git-delta_${GIT_DELTA_VERSION}_${ARCH}.deb" && \
|
||||
rm "git-delta_${GIT_DELTA_VERSION}_${ARCH}.deb"
|
||||
|
||||
# Set up non-root user
|
||||
USER node
|
||||
@@ -57,8 +64,13 @@ ENV PATH=$PATH:/usr/local/share/npm-global/bin
|
||||
# Set the default shell to zsh rather than sh
|
||||
ENV SHELL=/bin/zsh
|
||||
|
||||
# Set the default editor and visual
|
||||
ENV EDITOR nano
|
||||
ENV VISUAL nano
|
||||
|
||||
# Default powerline10k theme
|
||||
RUN sh -c "$(wget -O- https://github.com/deluan/zsh-in-docker/releases/download/v1.2.0/zsh-in-docker.sh)" -- \
|
||||
ARG ZSH_IN_DOCKER_VERSION=1.2.0
|
||||
RUN sh -c "$(wget -O- https://github.com/deluan/zsh-in-docker/releases/download/v${ZSH_IN_DOCKER_VERSION}/zsh-in-docker.sh)" -- \
|
||||
-p git \
|
||||
-p fzf \
|
||||
-a "source /usr/share/doc/fzf/examples/key-bindings.zsh" \
|
||||
@@ -67,7 +79,8 @@ RUN sh -c "$(wget -O- https://github.com/deluan/zsh-in-docker/releases/download/
|
||||
-x
|
||||
|
||||
# Install Claude
|
||||
RUN npm install -g @anthropic-ai/claude-code
|
||||
RUN npm install -g @anthropic-ai/claude-code@${CLAUDE_CODE_VERSION}
|
||||
|
||||
|
||||
# Copy and set up firewall script
|
||||
COPY init-firewall.sh /usr/local/bin/
|
||||
|
||||
@@ -3,7 +3,10 @@
|
||||
"build": {
|
||||
"dockerfile": "Dockerfile",
|
||||
"args": {
|
||||
"TZ": "${localEnv:TZ:America/Los_Angeles}"
|
||||
"TZ": "${localEnv:TZ:America/Los_Angeles}",
|
||||
"CLAUDE_CODE_VERSION": "latest",
|
||||
"GIT_DELTA_VERSION": "0.18.2",
|
||||
"ZSH_IN_DOCKER_VERSION": "1.2.0"
|
||||
}
|
||||
},
|
||||
"runArgs": [
|
||||
@@ -38,10 +41,10 @@
|
||||
},
|
||||
"remoteUser": "node",
|
||||
"mounts": [
|
||||
"source=claude-code-bashhistory,target=/commandhistory,type=volume",
|
||||
"source=claude-code-config,target=/home/node/.claude,type=volume"
|
||||
"source=claude-code-bashhistory-${devcontainerId},target=/commandhistory,type=volume",
|
||||
"source=claude-code-config-${devcontainerId},target=/home/node/.claude,type=volume"
|
||||
],
|
||||
"remoteEnv": {
|
||||
"containerEnv": {
|
||||
"NODE_OPTIONS": "--max-old-space-size=4096",
|
||||
"CLAUDE_CONFIG_DIR": "/home/node/.claude",
|
||||
"POWERLEVEL9K_DISABLE_GITSTATUS": "true"
|
||||
|
||||
@@ -2,6 +2,9 @@
|
||||
set -euo pipefail # Exit on error, undefined vars, and pipeline failures
|
||||
IFS=$'\n\t' # Stricter word splitting
|
||||
|
||||
# 1. Extract Docker DNS info BEFORE any flushing
|
||||
DOCKER_DNS_RULES=$(iptables-save -t nat | grep "127\.0\.0\.11" || true)
|
||||
|
||||
# Flush existing rules and delete existing ipsets
|
||||
iptables -F
|
||||
iptables -X
|
||||
@@ -11,6 +14,16 @@ iptables -t mangle -F
|
||||
iptables -t mangle -X
|
||||
ipset destroy allowed-domains 2>/dev/null || true
|
||||
|
||||
# 2. Selectively restore ONLY internal Docker DNS resolution
|
||||
if [ -n "$DOCKER_DNS_RULES" ]; then
|
||||
echo "Restoring Docker DNS rules..."
|
||||
iptables -t nat -N DOCKER_OUTPUT 2>/dev/null || true
|
||||
iptables -t nat -N DOCKER_POSTROUTING 2>/dev/null || true
|
||||
echo "$DOCKER_DNS_RULES" | xargs -L 1 iptables -t nat
|
||||
else
|
||||
echo "No Docker DNS rules to restore"
|
||||
fi
|
||||
|
||||
# First allow DNS and localhost before any restrictions
|
||||
# Allow outbound DNS
|
||||
iptables -A OUTPUT -p udp --dport 53 -j ACCEPT
|
||||
@@ -58,7 +71,7 @@ for domain in \
|
||||
"statsig.anthropic.com" \
|
||||
"statsig.com"; do
|
||||
echo "Resolving $domain..."
|
||||
ips=$(dig +short A "$domain")
|
||||
ips=$(dig +noall +answer A "$domain" | awk '$4 == "A" {print $5}')
|
||||
if [ -z "$ips" ]; then
|
||||
echo "ERROR: Failed to resolve $domain"
|
||||
exit 1
|
||||
|
||||
31
.github/workflows/auto-close-duplicates.yml
vendored
Normal file
31
.github/workflows/auto-close-duplicates.yml
vendored
Normal file
@@ -0,0 +1,31 @@
|
||||
name: Auto-close duplicate issues
|
||||
description: Auto-closes issues that are duplicates of existing issues
|
||||
on:
|
||||
schedule:
|
||||
- cron: "0 9 * * *"
|
||||
workflow_dispatch:
|
||||
|
||||
jobs:
|
||||
auto-close-duplicates:
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 10
|
||||
permissions:
|
||||
contents: read
|
||||
issues: write
|
||||
|
||||
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 }}
|
||||
44
.github/workflows/backfill-duplicate-comments.yml
vendored
Normal file
44
.github/workflows/backfill-duplicate-comments.yml
vendored
Normal file
@@ -0,0 +1,44 @@
|
||||
name: Backfill Duplicate Comments
|
||||
description: Triggers duplicate detection for old issues that don't have duplicate comments
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
days_back:
|
||||
description: 'How many days back to look for old issues'
|
||||
required: false
|
||||
default: '90'
|
||||
type: string
|
||||
dry_run:
|
||||
description: 'Dry run mode (true to only log what would be done)'
|
||||
required: false
|
||||
default: 'true'
|
||||
type: choice
|
||||
options:
|
||||
- 'true'
|
||||
- 'false'
|
||||
|
||||
jobs:
|
||||
backfill-duplicate-comments:
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 30
|
||||
permissions:
|
||||
contents: read
|
||||
issues: read
|
||||
actions: write
|
||||
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Setup Bun
|
||||
uses: oven-sh/setup-bun@v2
|
||||
with:
|
||||
bun-version: latest
|
||||
|
||||
- name: Backfill duplicate comments
|
||||
run: bun run scripts/backfill-duplicate-comments.ts
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
DAYS_BACK: ${{ inputs.days_back }}
|
||||
DRY_RUN: ${{ inputs.dry_run }}
|
||||
80
.github/workflows/claude-dedupe-issues.yml
vendored
Normal file
80
.github/workflows/claude-dedupe-issues.yml
vendored
Normal file
@@ -0,0 +1,80 @@
|
||||
name: Claude Issue Dedupe
|
||||
description: Automatically dedupe GitHub issues using Claude Code
|
||||
on:
|
||||
issues:
|
||||
types: [opened]
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
issue_number:
|
||||
description: 'Issue number to process for duplicate detection'
|
||||
required: true
|
||||
type: string
|
||||
|
||||
jobs:
|
||||
claude-dedupe-issues:
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 10
|
||||
permissions:
|
||||
contents: read
|
||||
issues: write
|
||||
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Run Claude Code slash command
|
||||
uses: anthropics/claude-code-base-action@beta
|
||||
with:
|
||||
prompt: "/dedupe ${{ github.repository }}/issues/${{ github.event.issue.number || inputs.issue_number }}"
|
||||
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
|
||||
3
.github/workflows/claude-issue-triage.yml
vendored
3
.github/workflows/claude-issue-triage.yml
vendored
@@ -13,6 +13,9 @@ jobs:
|
||||
issues: write
|
||||
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Create triage prompt
|
||||
run: |
|
||||
mkdir -p /tmp/claude-prompts
|
||||
|
||||
92
.github/workflows/lock-closed-issues.yml
vendored
Normal file
92
.github/workflows/lock-closed-issues.yml
vendored
Normal file
@@ -0,0 +1,92 @@
|
||||
name: "Lock Stale Issues"
|
||||
|
||||
on:
|
||||
schedule:
|
||||
# 8am Pacific = 1pm UTC (2pm UTC during DST)
|
||||
- cron: "0 14 * * *"
|
||||
workflow_dispatch:
|
||||
|
||||
permissions:
|
||||
issues: write
|
||||
|
||||
concurrency:
|
||||
group: lock-threads
|
||||
|
||||
jobs:
|
||||
lock-closed-issues:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Lock closed issues after 7 days of inactivity
|
||||
uses: actions/github-script@v7
|
||||
with:
|
||||
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}`);
|
||||
40
.github/workflows/log-issue-events.yml
vendored
Normal file
40
.github/workflows/log-issue-events.yml
vendored
Normal file
@@ -0,0 +1,40 @@
|
||||
name: Log Issue Events to Statsig
|
||||
|
||||
on:
|
||||
issues:
|
||||
types: [opened, closed]
|
||||
|
||||
jobs:
|
||||
log-to-statsig:
|
||||
runs-on: ubuntu-latest
|
||||
permissions:
|
||||
issues: read
|
||||
steps:
|
||||
- name: Log issue creation to Statsig
|
||||
env:
|
||||
STATSIG_API_KEY: ${{ secrets.STATSIG_API_KEY }}
|
||||
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 }}
|
||||
run: |
|
||||
# All values are now safely passed via environment variables
|
||||
# No direct templating in the shell script to prevent injection attacks
|
||||
|
||||
curl -X POST "https://events.statsigapi.net/v1/log_event" \
|
||||
-H "Content-Type: application/json" \
|
||||
-H "statsig-api-key: $STATSIG_API_KEY" \
|
||||
-d '{
|
||||
"events": [{
|
||||
"eventName": "github_issue_created",
|
||||
"metadata": {
|
||||
"issue_number": "'"$ISSUE_NUMBER"'",
|
||||
"repository": "'"$REPO"'",
|
||||
"title": "'"$(echo "$ISSUE_TITLE" | sed "s/\"/\\\\\"/g")"'",
|
||||
"author": "'"$AUTHOR"'",
|
||||
"created_at": "'"$CREATED_AT"'"
|
||||
},
|
||||
"time": '"$(date +%s)000"'
|
||||
}]
|
||||
}'
|
||||
248
CHANGELOG.md
248
CHANGELOG.md
@@ -1,8 +1,252 @@
|
||||
# Changelog
|
||||
|
||||
## 1.0.88
|
||||
|
||||
- Fixed issue causing "OAuth authentication is currently not supported"
|
||||
- Status line input now includes `exceeds_200k_tokens`
|
||||
- Fixed incorrect usage tracking in /cost.
|
||||
- Introduced `ANTHROPIC_DEFAULT_SONNET_MODEL` and `ANTHROPIC_DEFAULT_OPUS_MODEL` for controlling model aliases opusplan, opus, and sonnet.
|
||||
- Bedrock: Updated default Sonnet model to Sonnet 4
|
||||
|
||||
## 1.0.86
|
||||
|
||||
- Added /context to help users self-serve debug context issues
|
||||
- SDK: Added UUID support for all SDK messages
|
||||
- SDK: Added `--replay-user-messages` to replay user messages back to stdout
|
||||
|
||||
## 1.0.85
|
||||
|
||||
- Status line input now includes session cost info
|
||||
- Hooks: Introduced SessionEnd hook
|
||||
|
||||
## 1.0.84
|
||||
|
||||
- Fix tool_use/tool_result id mismatch error when network is unstable
|
||||
- Fix Claude sometimes ignoring real-time steering when wrapping up a task
|
||||
- @-mention: Add ~/.claude/\* files to suggestions for easier agent, output style, and slash command editing
|
||||
- Use built-in ripgrep by default; to opt out of this behavior, set USE_BUILTIN_RIPGREP=0
|
||||
|
||||
## 1.0.83
|
||||
|
||||
- @-mention: Support files with spaces in path
|
||||
- New shimmering spinner
|
||||
|
||||
## 1.0.82
|
||||
|
||||
- SDK: Add request cancellation support
|
||||
- SDK: New additionalDirectories option to search custom paths, improved slash command processing
|
||||
- Settings: Validation prevents invalid fields in .claude/settings.json files
|
||||
- MCP: Improve tool name consistency
|
||||
- Bash: Fix crash when Claude tries to automatically read large files
|
||||
|
||||
## 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.)
|
||||
- Customizable status line: add your terminal prompt to Claude Code with /statusline
|
||||
|
||||
## 1.0.70
|
||||
|
||||
- Performance: Optimized message rendering for better performance with large contexts
|
||||
- Windows: Fixed native file search, ripgrep, and subagent functionality
|
||||
- Added support for @-mentions in slash command arguments
|
||||
|
||||
## 1.0.69
|
||||
|
||||
- Upgraded Opus to version 4.1
|
||||
|
||||
## 1.0.68
|
||||
|
||||
- Fix incorrect model names being used for certain commands like `/pr-comments`
|
||||
- Windows: improve permissions checks for allow / deny tools and project trust. This may create a new project entry in `.claude.json` - manually merge the history field if desired.
|
||||
- Windows: improve sub-process spawning to eliminate "No such file or directory" when running commands like pnpm
|
||||
- Enhanced /doctor command with CLAUDE.md and MCP tool context for self-serve debugging
|
||||
- SDK: Added canUseTool callback support for tool confirmation
|
||||
- Added `disableAllHooks` setting
|
||||
- Improved file suggestions performance in large repos
|
||||
|
||||
## 1.0.65
|
||||
|
||||
- IDE: Fixed connection stability issues and error handling for diagnostics
|
||||
- Windows: Fixed shell environment setup for users without .bashrc files
|
||||
|
||||
## 1.0.64
|
||||
|
||||
- Agents: Added model customization support - you can now specify which model an agent should use
|
||||
- Agents: Fixed unintended access to the recursive agent tool
|
||||
- Hooks: Added systemMessage field to hook JSON output for displaying warnings and context
|
||||
- SDK: Fixed user input tracking across multi-turn conversations
|
||||
- Added hidden files to file search and @-mention suggestions
|
||||
|
||||
## 1.0.63
|
||||
|
||||
- Windows: Fixed file search, @agent mentions, and custom slash commands functionality
|
||||
|
||||
## 1.0.62
|
||||
|
||||
- Added @-mention support with typeahead for custom agents. @<your-custom-agent> to invoke it
|
||||
- Hooks: Added SessionStart hook for new session initialization
|
||||
- /add-dir command now supports typeahead for directory paths
|
||||
- Improved network connectivity check reliability
|
||||
|
||||
## 1.0.61
|
||||
|
||||
- Transcript mode (Ctrl+R): Changed Esc to exit transcript mode rather than interrupt
|
||||
- Settings: Added `--settings` flag to load settings from a JSON file
|
||||
- Settings: Fixed resolution of settings files paths that are symlinks
|
||||
- OTEL: Fixed reporting of wrong organization after authentication changes
|
||||
- Slash commands: Fixed permissions checking for allowed-tools with Bash
|
||||
- IDE: Added support for pasting images in VSCode MacOS using ⌘+V
|
||||
- IDE: Added `CLAUDE_CODE_AUTO_CONNECT_IDE=false` for disabling IDE auto-connection
|
||||
- Added `CLAUDE_CODE_SHELL_PREFIX` for wrapping Claude and user-provided shell commands run by Claude Code
|
||||
|
||||
## 1.0.60
|
||||
|
||||
- You can now create custom subagents for specialized tasks! Run /agents to get started
|
||||
|
||||
## 1.0.59
|
||||
|
||||
- SDK: Added tool confirmation support with canUseTool callback
|
||||
- SDK: Allow specifying env for spawned process
|
||||
- Hooks: Exposed PermissionDecision to hooks (including "ask")
|
||||
- Hooks: UserPromptSubmit now supports additionalContext in advanced JSON output
|
||||
- Fixed issue where some Max users that specified Opus would still see fallback to Sonnet
|
||||
|
||||
## 1.0.58
|
||||
|
||||
- Added support for reading PDFs
|
||||
- MCP: Improved server health status display in 'claude mcp list'
|
||||
- Hooks: Added CLAUDE_PROJECT_DIR env var for hook commands
|
||||
|
||||
## 1.0.57
|
||||
|
||||
- Added support for specifying a model in slash commands
|
||||
- Improved permission messages to help Claude understand allowed tools
|
||||
- Fix: Remove trailing newlines from bash output in terminal wrapping
|
||||
|
||||
## 1.0.56
|
||||
|
||||
- Windows: Enabled shift+tab for mode switching on versions of Node.js that support terminal VT mode
|
||||
- Fixes for WSL IDE detection
|
||||
- Fix an issue causing awsRefreshHelper changes to .aws directory not to be picked up
|
||||
|
||||
## 1.0.55
|
||||
|
||||
- Clarified knowledge cutoff for Opus 4 and Sonnet 4 models
|
||||
- Windows: fixed Ctrl+Z crash
|
||||
- SDK: Added ability to capture error logging
|
||||
- Add --system-prompt-file option to override system prompt in print mode
|
||||
|
||||
## 1.0.54
|
||||
|
||||
- Hooks: Added UserPromptSubmit hook and the current working directory to hook inputs
|
||||
- Custom slash commands: Added argument-hint to frontmatter
|
||||
- Windows: OAuth uses port 45454 and properly constructs browser URL
|
||||
- Windows: mode switching now uses alt + m, and plan mode renders properly
|
||||
- Shell: Switch to in-memory shell snapshot to fix file-related errors
|
||||
|
||||
## 1.0.53
|
||||
|
||||
- Updated @-mention file truncation from 100 lines to 2000 lines
|
||||
- Add helper script settings for AWS token refresh: awsAuthRefresh (for foreground operations like aws sso login) and awsCredentialExport (for background operation with STS-like response).
|
||||
|
||||
## 1.0.52
|
||||
|
||||
- Added support for MCP server instructions
|
||||
|
||||
## 1.0.51
|
||||
|
||||
- Added support for native Windows (requires Git for Windows)
|
||||
- Added support for Bedrock API keys through environment variable AWS_BEARER_TOKEN_BEDROCK
|
||||
- Settings: /doctor can now help you identify and fix invalid setting files
|
||||
- `--append-system-prompt` can now be used in interactive mode, not just --print/-p.
|
||||
- Increased auto-compact warning threshold from 60% to 80%
|
||||
- Fixed an issue with handling user directories with spaces for shell snapshots
|
||||
- OTEL resource now includes os.type, os.version, host.arch, and wsl.version (if running on Windows Subsystem for Linux)
|
||||
- Custom slash commands: Fixed user-level commands in subdirectories
|
||||
- Plan mode: Fixed issue where rejected plan from sub-task would get discarded
|
||||
|
||||
## 1.0.48
|
||||
|
||||
- Fixed a bug in v1.0.45 where the app would sometimes freeze on launch
|
||||
- Added progress messages to Bash tool based on the last 5 lines of command output
|
||||
- Added expanding variables support for MCP server configuration
|
||||
- Moved shell snapshots from /tmp to ~/.claude for more reliable Bash tool calls
|
||||
- Improved IDE extension path handling when Claude Code runs in WSL
|
||||
- Hooks: Added a PreCompact hook
|
||||
- Vim mode: Added c, f/F, t/T
|
||||
|
||||
## 1.0.45
|
||||
|
||||
- Redesigned Search (Grep) tool with new tool input parameters and features
|
||||
- Disabled IDE diffs for notebook files, fixing "Timeout waiting after 1000ms" error
|
||||
- Fixed config file corruption issue by enforcing atomic writes
|
||||
- Updated prompt input undo to Ctrl+\_ to avoid breaking existing Ctrl+U behavior, matching zsh's undo shortcut
|
||||
- Stop Hooks: Fixed transcript path after /clear and fixed triggering when loop ends with tool call
|
||||
- Custom slash commands: Restored namespacing in command names based on subdirectories. For example, .claude/commands/frontend/component.md is now /frontend:component, not /component.
|
||||
|
||||
## 1.0.44
|
||||
|
||||
- New /export command lets you quickly export a conversation for sharing
|
||||
- MCP: resource_link tool results are now supported
|
||||
- MCP: tool annotations and tool titles now display in /mcp view
|
||||
- Changed Ctrl+Z to suspend Claude Code. Resume by running `fg`. Prompt input undo is now Ctrl+U.
|
||||
|
||||
## 1.0.43
|
||||
|
||||
- Fixed a bug where the theme selector was saving excessively
|
||||
- Hooks: Added EPIPE system error handling
|
||||
|
||||
## 1.0.42
|
||||
|
||||
- Added tilde (`~`) expansion support to `/add-dir` command
|
||||
|
||||
## 1.0.41
|
||||
|
||||
- Hooks: Split Stop hook triggering into Stop and SubagentStop
|
||||
- Hooks: Enabled optional timeout configuration for each command
|
||||
- Hooks: Added "hook_event_name" to hook input
|
||||
- Fixed a bug where MCP tools would display twice in tool list
|
||||
- New tool parameters JSON for Bash tool in `tool_decision` event
|
||||
|
||||
## 1.0.40
|
||||
|
||||
- Fixed a bug causing API connection errors with UNABLE_TO_GET_ISSUER_CERT_LOCALLY if `NODE_EXTRA_CA_CERTS` was set
|
||||
|
||||
## 1.0.39
|
||||
|
||||
- New Active Time metric in OpenTelemetry logging
|
||||
|
||||
## 1.0.38
|
||||
|
||||
- Released [hooks](https://docs.anthropic.com/en/docs/claude-code/hooks). Special thanks to community input in [Github Issues](https://github.com/anthropics/claude-code/issues/712)
|
||||
- Released hooks. Special thanks to community input in https://github.com/anthropics/claude-code/issues/712. Docs: https://docs.anthropic.com/en/docs/claude-code/hooks
|
||||
|
||||
## 1.0.37
|
||||
|
||||
@@ -24,7 +268,7 @@
|
||||
## 1.0.33
|
||||
|
||||
- Improved logging functionality with session ID support
|
||||
- Added undo functionality (Ctrl+Z and vim 'u' command)
|
||||
- Added prompt input undo functionality (Ctrl+Z and vim 'u' command)
|
||||
- Improvements to plan mode
|
||||
|
||||
## 1.0.32
|
||||
|
||||
@@ -24,6 +24,10 @@ npm install -g @anthropic-ai/claude-code
|
||||
|
||||
We welcome your feedback. Use the `/bug` command to report issues directly within Claude Code, or file a [GitHub issue](https://github.com/anthropics/claude-code/issues).
|
||||
|
||||
## Connect on Discord
|
||||
|
||||
Join the [Claude Developers Discord](https://anthropic.com/discord) to connect with other developers using Claude Code. Get help, share feedback, and discuss your projects with the community.
|
||||
|
||||
## Data collection, usage, and retention
|
||||
|
||||
When you use Claude Code, we collect feedback, which includes usage data (such as code acceptance or rejections), associated conversation data, and user feedback submitted via the `/bug` command.
|
||||
|
||||
148
Script/run_devcontainer_claude_code.ps1
Normal file
148
Script/run_devcontainer_claude_code.ps1
Normal file
@@ -0,0 +1,148 @@
|
||||
<#
|
||||
.SYNOPSIS
|
||||
Automates the setup and connection to a DevContainer environment using either Docker or Podman on Windows.
|
||||
|
||||
.DESCRIPTION
|
||||
This script automates the process of initializing, starting, and connecting to a DevContainer
|
||||
using either Docker or Podman as the container backend. It must be executed from the root
|
||||
directory of your project and assumes the script is located in a 'Script' subdirectory.
|
||||
|
||||
.PARAMETER Backend
|
||||
Specifies the container backend to use. Valid values are 'docker' or 'podman'.
|
||||
|
||||
.EXAMPLE
|
||||
.\Script\run_devcontainer_claude_code.ps1 -Backend docker
|
||||
Uses Docker as the container backend.
|
||||
|
||||
.EXAMPLE
|
||||
.\Script\run_devcontainer_claude_code.ps1 -Backend podman
|
||||
Uses Podman as the container backend.
|
||||
|
||||
.NOTES
|
||||
Project Structure:
|
||||
Project/
|
||||
├── .devcontainer/
|
||||
└── Script/
|
||||
└── run_devcontainer_claude_code.ps1
|
||||
#>
|
||||
|
||||
[CmdletBinding()]
|
||||
param(
|
||||
[Parameter(Mandatory=$true)]
|
||||
[ValidateSet('docker','podman')]
|
||||
[string]$Backend
|
||||
)
|
||||
|
||||
# Notify script start
|
||||
Write-Host "--- DevContainer Startup & Connection Script ---"
|
||||
Write-Host "Using backend: $($Backend)"
|
||||
|
||||
# --- Prerequisite Check ---
|
||||
Write-Host "Checking for required commands..."
|
||||
try {
|
||||
Get-Command $Backend -ErrorAction Stop | Out-Null
|
||||
Write-Host "- $($Backend) command found."
|
||||
Get-Command devcontainer -ErrorAction Stop | Out-Null
|
||||
Write-Host "- devcontainer command found."
|
||||
}
|
||||
catch {
|
||||
Write-Error "A required command is not installed or not in your PATH."
|
||||
Write-Error "Please ensure '$($_.Exception.Message.Split(':')[0])' and 'devcontainer' are installed and accessible."
|
||||
exit 1
|
||||
}
|
||||
|
||||
|
||||
# --- Backend-Specific Initialization ---
|
||||
if ($Backend -eq 'podman') {
|
||||
Write-Host "--- Podman Backend Initialization ---"
|
||||
|
||||
# --- Step 1a: Initialize Podman machine ---
|
||||
Write-Host "Initializing Podman machine 'claudeVM'..."
|
||||
try {
|
||||
& podman machine init claudeVM
|
||||
Write-Host "Podman machine 'claudeVM' initialized or already exists."
|
||||
} catch {
|
||||
Write-Error "Failed to initialize Podman machine: $($_.Exception.Message)"
|
||||
exit 1 # Exit script on error
|
||||
}
|
||||
|
||||
# --- Step 1b: Start Podman machine ---
|
||||
Write-Host "Starting Podman machine 'claudeVM'..."
|
||||
try {
|
||||
& podman machine start claudeVM -q
|
||||
Write-Host "Podman machine started or already running."
|
||||
} catch {
|
||||
Write-Error "Failed to start Podman machine: $($_.Exception.Message)"
|
||||
exit 1
|
||||
}
|
||||
|
||||
# --- Step 2: Set default connection ---
|
||||
Write-Host "Setting default Podman connection to 'claudeVM'..."
|
||||
try {
|
||||
& podman system connection default claudeVM
|
||||
Write-Host "Default connection set."
|
||||
} catch {
|
||||
Write-Warning "Failed to set default Podman connection (may be already set or machine issue): $($_.Exception.Message)"
|
||||
}
|
||||
|
||||
} elseif ($Backend -eq 'docker') {
|
||||
Write-Host "--- Docker Backend Initialization ---"
|
||||
|
||||
# --- Step 1 & 2: Check Docker Desktop ---
|
||||
Write-Host "Checking if Docker Desktop is running and docker command is available..."
|
||||
try {
|
||||
docker info | Out-Null
|
||||
Write-Host "Docker Desktop (daemon) is running."
|
||||
} catch {
|
||||
Write-Error "Docker Desktop is not running or docker command not found."
|
||||
Write-Error "Please ensure Docker Desktop is running."
|
||||
exit 1
|
||||
}
|
||||
}
|
||||
|
||||
# --- Step 3: Bring up DevContainer ---
|
||||
Write-Host "Bringing up DevContainer in the current folder..."
|
||||
try {
|
||||
$arguments = @('up', '--workspace-folder', '.')
|
||||
if ($Backend -eq 'podman') {
|
||||
$arguments += '--docker-path', 'podman'
|
||||
}
|
||||
& devcontainer @arguments
|
||||
Write-Host "DevContainer startup process completed."
|
||||
} catch {
|
||||
Write-Error "Failed to bring up DevContainer: $($_.Exception.Message)"
|
||||
exit 1
|
||||
}
|
||||
|
||||
# --- Step 4: Get DevContainer ID ---
|
||||
Write-Host "Finding the DevContainer ID..."
|
||||
$currentFolder = (Get-Location).Path
|
||||
|
||||
try {
|
||||
$containerId = (& $Backend ps --filter "label=devcontainer.local_folder=$currentFolder" --format '{{.ID}}').Trim()
|
||||
} catch {
|
||||
$displayCommand = "$Backend ps --filter `"label=devcontainer.local_folder=$currentFolder`" --format '{{.ID}}'"
|
||||
Write-Error "Failed to get container ID (Command: $displayCommand): $($_.Exception.Message)"
|
||||
exit 1
|
||||
}
|
||||
|
||||
if (-not $containerId) {
|
||||
Write-Error "Could not find DevContainer ID for the current folder ('$currentFolder')."
|
||||
Write-Error "Please check if 'devcontainer up' was successful and the container is running."
|
||||
exit 1
|
||||
}
|
||||
Write-Host "Found container ID: $containerId"
|
||||
|
||||
# --- Step 5 & 6: Execute command and enter interactive shell inside container ---
|
||||
Write-Host "Executing 'claude' command and then starting zsh session inside container $($containerId)..."
|
||||
try {
|
||||
& $Backend exec -it $containerId zsh -c 'claude; exec zsh'
|
||||
Write-Host "Interactive session ended."
|
||||
} catch {
|
||||
$displayCommand = "$Backend exec -it $containerId zsh -c 'claude; exec zsh'"
|
||||
Write-Error "Failed to execute command inside container (Command: $displayCommand): $($_.Exception.Message)"
|
||||
exit 1
|
||||
}
|
||||
|
||||
# Notify script completion
|
||||
Write-Host "--- Script completed ---"
|
||||
83
examples/hooks/bash_command_validator_example.py
Normal file
83
examples/hooks/bash_command_validator_example.py
Normal file
@@ -0,0 +1,83 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
Claude Code Hook: Bash Command Validator
|
||||
=========================================
|
||||
This hook runs as a PreToolUse hook for the Bash tool.
|
||||
It validates bash commands against a set of rules before execution.
|
||||
In this case it changes grep calls to using rg.
|
||||
|
||||
Read more about hooks here: https://docs.anthropic.com/en/docs/claude-code/hooks
|
||||
|
||||
Make sure to change your path to your actual script.
|
||||
|
||||
{
|
||||
"hooks": {
|
||||
"PreToolUse": [
|
||||
{
|
||||
"matcher": "Bash",
|
||||
"hooks": [
|
||||
{
|
||||
"type": "command",
|
||||
"command": "python3 /path/to/claude-code/examples/hooks/bash_command_validator_example.py"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
"""
|
||||
|
||||
import json
|
||||
import re
|
||||
import sys
|
||||
|
||||
# Define validation rules as a list of (regex pattern, message) tuples
|
||||
_VALIDATION_RULES = [
|
||||
(
|
||||
r"^grep\b(?!.*\|)",
|
||||
"Use 'rg' (ripgrep) instead of 'grep' for better performance and features",
|
||||
),
|
||||
(
|
||||
r"^find\s+\S+\s+-name\b",
|
||||
"Use 'rg --files | rg pattern' or 'rg --files -g pattern' instead of 'find -name' for better performance",
|
||||
),
|
||||
]
|
||||
|
||||
|
||||
def _validate_command(command: str) -> list[str]:
|
||||
issues = []
|
||||
for pattern, message in _VALIDATION_RULES:
|
||||
if re.search(pattern, command):
|
||||
issues.append(message)
|
||||
return issues
|
||||
|
||||
|
||||
def main():
|
||||
try:
|
||||
input_data = json.load(sys.stdin)
|
||||
except json.JSONDecodeError as e:
|
||||
print(f"Error: Invalid JSON input: {e}", file=sys.stderr)
|
||||
# Exit code 1 shows stderr to the user but not to Claude
|
||||
sys.exit(1)
|
||||
|
||||
tool_name = input_data.get("tool_name", "")
|
||||
if tool_name != "Bash":
|
||||
sys.exit(0)
|
||||
|
||||
tool_input = input_data.get("tool_input", {})
|
||||
command = tool_input.get("command", "")
|
||||
|
||||
if not command:
|
||||
sys.exit(0)
|
||||
|
||||
issues = _validate_command(command)
|
||||
if issues:
|
||||
for message in issues:
|
||||
print(f"• {message}", file=sys.stderr)
|
||||
# Exit code 2 blocks tool call and shows stderr to Claude
|
||||
sys.exit(2)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
277
scripts/auto-close-duplicates.ts
Normal file
277
scripts/auto-close-duplicates.ts
Normal file
@@ -0,0 +1,277 @@
|
||||
#!/usr/bin/env bun
|
||||
|
||||
declare global {
|
||||
var process: {
|
||||
env: Record<string, string | undefined>;
|
||||
};
|
||||
}
|
||||
|
||||
interface GitHubIssue {
|
||||
number: number;
|
||||
title: string;
|
||||
user: { id: number };
|
||||
created_at: string;
|
||||
}
|
||||
|
||||
interface GitHubComment {
|
||||
id: number;
|
||||
body: string;
|
||||
created_at: string;
|
||||
user: { type: string; id: number };
|
||||
}
|
||||
|
||||
interface GitHubReaction {
|
||||
user: { id: number };
|
||||
content: string;
|
||||
}
|
||||
|
||||
async function githubRequest<T>(endpoint: string, token: string, method: string = 'GET', body?: any): Promise<T> {
|
||||
const response = await fetch(`https://api.github.com${endpoint}`, {
|
||||
method,
|
||||
headers: {
|
||||
Authorization: `Bearer ${token}`,
|
||||
Accept: "application/vnd.github.v3+json",
|
||||
"User-Agent": "auto-close-duplicates-script",
|
||||
...(body && { "Content-Type": "application/json" }),
|
||||
},
|
||||
...(body && { body: JSON.stringify(body) }),
|
||||
});
|
||||
|
||||
if (!response.ok) {
|
||||
throw new Error(
|
||||
`GitHub API request failed: ${response.status} ${response.statusText}`
|
||||
);
|
||||
}
|
||||
|
||||
return response.json();
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
|
||||
async function closeIssueAsDuplicate(
|
||||
owner: string,
|
||||
repo: string,
|
||||
issueNumber: number,
|
||||
duplicateOfNumber: number,
|
||||
token: string
|
||||
): Promise<void> {
|
||||
await githubRequest(
|
||||
`/repos/${owner}/${repo}/issues/${issueNumber}`,
|
||||
token,
|
||||
'PATCH',
|
||||
{
|
||||
state: 'closed',
|
||||
state_reason: 'duplicate',
|
||||
labels: ['duplicate']
|
||||
}
|
||||
);
|
||||
|
||||
await githubRequest(
|
||||
`/repos/${owner}/${repo}/issues/${issueNumber}/comments`,
|
||||
token,
|
||||
'POST',
|
||||
{
|
||||
body: `This issue has been automatically closed as a duplicate of #${duplicateOfNumber}.
|
||||
|
||||
If this is incorrect, please re-open this issue or create a new one.
|
||||
|
||||
🤖 Generated with [Claude Code](https://claude.ai/code)`
|
||||
}
|
||||
);
|
||||
|
||||
}
|
||||
|
||||
async function autoCloseDuplicates(): Promise<void> {
|
||||
console.log("[DEBUG] Starting auto-close duplicates script");
|
||||
|
||||
const token = process.env.GITHUB_TOKEN;
|
||||
if (!token) {
|
||||
throw new Error("GITHUB_TOKEN environment variable is required");
|
||||
}
|
||||
console.log("[DEBUG] GitHub token found");
|
||||
|
||||
const owner = process.env.GITHUB_REPOSITORY_OWNER || "anthropics";
|
||||
const repo = process.env.GITHUB_REPOSITORY_NAME || "claude-code";
|
||||
console.log(`[DEBUG] Repository: ${owner}/${repo}`);
|
||||
|
||||
const threeDaysAgo = new Date();
|
||||
threeDaysAgo.setDate(threeDaysAgo.getDate() - 3);
|
||||
console.log(
|
||||
`[DEBUG] Checking for duplicate comments older than: ${threeDaysAgo.toISOString()}`
|
||||
);
|
||||
|
||||
console.log("[DEBUG] Fetching open issues created more than 3 days ago...");
|
||||
const allIssues: GitHubIssue[] = [];
|
||||
let page = 1;
|
||||
const perPage = 100;
|
||||
|
||||
while (true) {
|
||||
const pageIssues: GitHubIssue[] = await githubRequest(
|
||||
`/repos/${owner}/${repo}/issues?state=open&per_page=${perPage}&page=${page}`,
|
||||
token
|
||||
);
|
||||
|
||||
if (pageIssues.length === 0) break;
|
||||
|
||||
// Filter for issues created more than 3 days ago
|
||||
const oldEnoughIssues = pageIssues.filter(issue =>
|
||||
new Date(issue.created_at) <= threeDaysAgo
|
||||
);
|
||||
|
||||
allIssues.push(...oldEnoughIssues);
|
||||
page++;
|
||||
|
||||
// Safety limit to avoid infinite loops
|
||||
if (page > 20) break;
|
||||
}
|
||||
|
||||
const issues = allIssues;
|
||||
console.log(`[DEBUG] Found ${issues.length} open issues`);
|
||||
|
||||
let processedCount = 0;
|
||||
let candidateCount = 0;
|
||||
|
||||
for (const issue of issues) {
|
||||
processedCount++;
|
||||
console.log(
|
||||
`[DEBUG] Processing issue #${issue.number} (${processedCount}/${issues.length}): ${issue.title}`
|
||||
);
|
||||
|
||||
console.log(`[DEBUG] Fetching comments for issue #${issue.number}...`);
|
||||
const comments: GitHubComment[] = await githubRequest(
|
||||
`/repos/${owner}/${repo}/issues/${issue.number}/comments`,
|
||||
token
|
||||
);
|
||||
console.log(
|
||||
`[DEBUG] Issue #${issue.number} has ${comments.length} comments`
|
||||
);
|
||||
|
||||
const dupeComments = comments.filter(
|
||||
(comment) =>
|
||||
comment.body.includes("Found") &&
|
||||
comment.body.includes("possible duplicate") &&
|
||||
comment.user.type === "Bot"
|
||||
);
|
||||
console.log(
|
||||
`[DEBUG] Issue #${issue.number} has ${dupeComments.length} duplicate detection comments`
|
||||
);
|
||||
|
||||
if (dupeComments.length === 0) {
|
||||
console.log(
|
||||
`[DEBUG] Issue #${issue.number} - no duplicate comments found, skipping`
|
||||
);
|
||||
continue;
|
||||
}
|
||||
|
||||
const lastDupeComment = dupeComments[dupeComments.length - 1];
|
||||
const dupeCommentDate = new Date(lastDupeComment.created_at);
|
||||
console.log(
|
||||
`[DEBUG] Issue #${
|
||||
issue.number
|
||||
} - most recent duplicate comment from: ${dupeCommentDate.toISOString()}`
|
||||
);
|
||||
|
||||
if (dupeCommentDate > threeDaysAgo) {
|
||||
console.log(
|
||||
`[DEBUG] Issue #${issue.number} - duplicate comment is too recent, skipping`
|
||||
);
|
||||
continue;
|
||||
}
|
||||
console.log(
|
||||
`[DEBUG] Issue #${
|
||||
issue.number
|
||||
} - duplicate comment is old enough (${Math.floor(
|
||||
(Date.now() - dupeCommentDate.getTime()) / (1000 * 60 * 60 * 24)
|
||||
)} days)`
|
||||
);
|
||||
|
||||
const commentsAfterDupe = comments.filter(
|
||||
(comment) => new Date(comment.created_at) > dupeCommentDate
|
||||
);
|
||||
console.log(
|
||||
`[DEBUG] Issue #${issue.number} - ${commentsAfterDupe.length} comments after duplicate detection`
|
||||
);
|
||||
|
||||
if (commentsAfterDupe.length > 0) {
|
||||
console.log(
|
||||
`[DEBUG] Issue #${issue.number} - has activity after duplicate comment, skipping`
|
||||
);
|
||||
continue;
|
||||
}
|
||||
|
||||
console.log(
|
||||
`[DEBUG] Issue #${issue.number} - checking reactions on duplicate comment...`
|
||||
);
|
||||
const reactions: GitHubReaction[] = await githubRequest(
|
||||
`/repos/${owner}/${repo}/issues/comments/${lastDupeComment.id}/reactions`,
|
||||
token
|
||||
);
|
||||
console.log(
|
||||
`[DEBUG] Issue #${issue.number} - duplicate comment has ${reactions.length} reactions`
|
||||
);
|
||||
|
||||
const authorThumbsDown = reactions.some(
|
||||
(reaction) =>
|
||||
reaction.user.id === issue.user.id && reaction.content === "-1"
|
||||
);
|
||||
console.log(
|
||||
`[DEBUG] Issue #${issue.number} - author thumbs down reaction: ${authorThumbsDown}`
|
||||
);
|
||||
|
||||
if (authorThumbsDown) {
|
||||
console.log(
|
||||
`[DEBUG] Issue #${issue.number} - author disagreed with duplicate detection, skipping`
|
||||
);
|
||||
continue;
|
||||
}
|
||||
|
||||
const duplicateIssueNumber = extractDuplicateIssueNumber(lastDupeComment.body);
|
||||
if (!duplicateIssueNumber) {
|
||||
console.log(
|
||||
`[DEBUG] Issue #${issue.number} - could not extract duplicate issue number from comment, skipping`
|
||||
);
|
||||
continue;
|
||||
}
|
||||
|
||||
candidateCount++;
|
||||
const issueUrl = `https://github.com/${owner}/${repo}/issues/${issue.number}`;
|
||||
|
||||
try {
|
||||
console.log(
|
||||
`[INFO] Auto-closing issue #${issue.number} as duplicate of #${duplicateIssueNumber}: ${issueUrl}`
|
||||
);
|
||||
await closeIssueAsDuplicate(owner, repo, issue.number, duplicateIssueNumber, token);
|
||||
console.log(
|
||||
`[SUCCESS] Successfully closed issue #${issue.number} as duplicate of #${duplicateIssueNumber}`
|
||||
);
|
||||
} catch (error) {
|
||||
console.error(
|
||||
`[ERROR] Failed to close issue #${issue.number} as duplicate: ${error}`
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
console.log(
|
||||
`[DEBUG] Script completed. Processed ${processedCount} issues, found ${candidateCount} candidates for auto-close`
|
||||
);
|
||||
}
|
||||
|
||||
autoCloseDuplicates().catch(console.error);
|
||||
|
||||
// Make it a module
|
||||
export {};
|
||||
198
scripts/backfill-duplicate-comments.ts
Normal file
198
scripts/backfill-duplicate-comments.ts
Normal file
@@ -0,0 +1,198 @@
|
||||
#!/usr/bin/env bun
|
||||
|
||||
declare global {
|
||||
var process: {
|
||||
env: Record<string, string | undefined>;
|
||||
};
|
||||
}
|
||||
|
||||
interface GitHubIssue {
|
||||
number: number;
|
||||
title: string;
|
||||
state: string;
|
||||
state_reason?: string;
|
||||
user: { id: number };
|
||||
created_at: string;
|
||||
closed_at?: string;
|
||||
}
|
||||
|
||||
interface GitHubComment {
|
||||
id: number;
|
||||
body: string;
|
||||
created_at: string;
|
||||
user: { type: string; id: number };
|
||||
}
|
||||
|
||||
async function githubRequest<T>(endpoint: string, token: string, method: string = 'GET', body?: any): Promise<T> {
|
||||
const response = await fetch(`https://api.github.com${endpoint}`, {
|
||||
method,
|
||||
headers: {
|
||||
Authorization: `Bearer ${token}`,
|
||||
Accept: "application/vnd.github.v3+json",
|
||||
"User-Agent": "backfill-duplicate-comments-script",
|
||||
...(body && { "Content-Type": "application/json" }),
|
||||
},
|
||||
...(body && { body: JSON.stringify(body) }),
|
||||
});
|
||||
|
||||
if (!response.ok) {
|
||||
throw new Error(
|
||||
`GitHub API request failed: ${response.status} ${response.statusText}`
|
||||
);
|
||||
}
|
||||
|
||||
return response.json();
|
||||
}
|
||||
|
||||
async function triggerDedupeWorkflow(
|
||||
owner: string,
|
||||
repo: string,
|
||||
issueNumber: number,
|
||||
token: string,
|
||||
dryRun: boolean = true
|
||||
): Promise<void> {
|
||||
if (dryRun) {
|
||||
console.log(`[DRY RUN] Would trigger dedupe workflow for issue #${issueNumber}`);
|
||||
return;
|
||||
}
|
||||
|
||||
await githubRequest(
|
||||
`/repos/${owner}/${repo}/actions/workflows/claude-dedupe-issues.yml/dispatches`,
|
||||
token,
|
||||
'POST',
|
||||
{
|
||||
ref: 'main',
|
||||
inputs: {
|
||||
issue_number: issueNumber.toString()
|
||||
}
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
async function backfillDuplicateComments(): Promise<void> {
|
||||
console.log("[DEBUG] Starting backfill duplicate comments script");
|
||||
|
||||
const token = process.env.GITHUB_TOKEN;
|
||||
if (!token) {
|
||||
throw new Error(`GITHUB_TOKEN environment variable is required
|
||||
|
||||
Usage:
|
||||
GITHUB_TOKEN=your_token bun run scripts/backfill-duplicate-comments.ts
|
||||
|
||||
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)`);
|
||||
}
|
||||
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);
|
||||
|
||||
console.log(`[DEBUG] Repository: ${owner}/${repo}`);
|
||||
console.log(`[DEBUG] Dry run mode: ${dryRun}`);
|
||||
console.log(`[DEBUG] Looking back ${daysBack} days`);
|
||||
|
||||
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}&since=${cutoffDate.toISOString()}`,
|
||||
token
|
||||
);
|
||||
|
||||
if (pageIssues.length === 0) break;
|
||||
|
||||
allIssues.push(...pageIssues);
|
||||
page++;
|
||||
|
||||
// Safety limit to avoid infinite loops
|
||||
if (page > 100) {
|
||||
console.log("[DEBUG] Reached page limit, stopping pagination");
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
console.log(`[DEBUG] Found ${allIssues.length} issues from the last ${daysBack} days`);
|
||||
|
||||
let processedCount = 0;
|
||||
let candidateCount = 0;
|
||||
let triggeredCount = 0;
|
||||
|
||||
for (const issue of allIssues) {
|
||||
processedCount++;
|
||||
console.log(
|
||||
`[DEBUG] Processing issue #${issue.number} (${processedCount}/${allIssues.length}): ${issue.title}`
|
||||
);
|
||||
|
||||
console.log(`[DEBUG] Fetching comments for issue #${issue.number}...`);
|
||||
const comments: GitHubComment[] = await githubRequest(
|
||||
`/repos/${owner}/${repo}/issues/${issue.number}/comments`,
|
||||
token
|
||||
);
|
||||
console.log(
|
||||
`[DEBUG] Issue #${issue.number} has ${comments.length} comments`
|
||||
);
|
||||
|
||||
// Look for existing duplicate detection comments (from the dedupe bot)
|
||||
const dupeDetectionComments = comments.filter(
|
||||
(comment) =>
|
||||
comment.body.includes("Found") &&
|
||||
comment.body.includes("possible duplicate") &&
|
||||
comment.user.type === "Bot"
|
||||
);
|
||||
|
||||
console.log(
|
||||
`[DEBUG] Issue #${issue.number} has ${dupeDetectionComments.length} duplicate detection comments`
|
||||
);
|
||||
|
||||
// Skip if there's already a duplicate detection comment
|
||||
if (dupeDetectionComments.length > 0) {
|
||||
console.log(
|
||||
`[DEBUG] Issue #${issue.number} already has duplicate detection comment, skipping`
|
||||
);
|
||||
continue;
|
||||
}
|
||||
|
||||
candidateCount++;
|
||||
const issueUrl = `https://github.com/${owner}/${repo}/issues/${issue.number}`;
|
||||
|
||||
try {
|
||||
console.log(
|
||||
`[INFO] ${dryRun ? '[DRY RUN] ' : ''}Triggering dedupe workflow for issue #${issue.number}: ${issueUrl}`
|
||||
);
|
||||
await triggerDedupeWorkflow(owner, repo, issue.number, token, dryRun);
|
||||
|
||||
if (!dryRun) {
|
||||
console.log(
|
||||
`[SUCCESS] Successfully triggered dedupe workflow for issue #${issue.number}`
|
||||
);
|
||||
}
|
||||
triggeredCount++;
|
||||
} catch (error) {
|
||||
console.error(
|
||||
`[ERROR] Failed to trigger workflow for issue #${issue.number}: ${error}`
|
||||
);
|
||||
}
|
||||
|
||||
// Add a delay between workflow triggers to avoid overwhelming the system
|
||||
await new Promise(resolve => setTimeout(resolve, 1000));
|
||||
}
|
||||
|
||||
console.log(
|
||||
`[DEBUG] Script completed. Processed ${processedCount} issues, found ${candidateCount} candidates without duplicate comments, ${dryRun ? 'would trigger' : 'triggered'} ${triggeredCount} workflows`
|
||||
);
|
||||
}
|
||||
|
||||
backfillDuplicateComments().catch(console.error);
|
||||
|
||||
// Make it a module
|
||||
export {};
|
||||
Reference in New Issue
Block a user