diff --git a/.claude/commands/dedupe.md b/.claude/commands/dedupe.md
index 9cd83aa8c..8e00ff444 100644
--- a/.claude/commands/dedupe.md
+++ b/.claude/commands/dedupe.md
@@ -1,5 +1,5 @@
---
-allowed-tools: Bash(gh issue view:*), Bash(gh search:*), Bash(gh issue list:*), Bash(gh api:*), Bash(gh issue comment:*)
+allowed-tools: Bash(gh issue view:*), Bash(gh search:*), Bash(gh issue list:*)
description: Find duplicate GitHub issues
---
@@ -7,32 +7,33 @@ 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.
+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 and write `{"skip": true, "reason": "..."}` to `/tmp/dedupe-result.json`.
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)
+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, write `{"skip": true, "reason": "no duplicates found"}` to `/tmp/dedupe-result.json` and do not proceed.
+5. Finally, write a JSON file to `/tmp/dedupe-result.json` with the list of duplicate issue URLs. Do NOT post comments directly.
+
+IMPORTANT: You must write output to `/tmp/dedupe-result.json` - never post comments directly. The workflow will handle posting.
+
+The JSON output format must be exactly:
+```json
+{
+ "duplicates": ["https://github.com/owner/repo/issues/123", "https://github.com/owner/repo/issues/456"]
+}
+```
+
+Or if skipping:
+```json
+{
+ "skip": true,
+ "reason": "reason for skipping"
+}
+```
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.)
+- Use `gh` to interact with Github for reading issues only, rather than web fetch
+- Do not use other tools beyond `gh` for reading (eg. don't use other MCP servers, file edit beyond the output JSON, etc.)
+- Do NOT use `gh issue comment` or `gh api` - these are not permitted for security reasons
- 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.
-2.
-3.
-
-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)
-
----
+- Output must be written to `/tmp/dedupe-result.json` - the workflow handles comment posting
diff --git a/.github/workflows/claude-dedupe-issues.yml b/.github/workflows/claude-dedupe-issues.yml
index afcdf9f53..d609b1f97 100644
--- a/.github/workflows/claude-dedupe-issues.yml
+++ b/.github/workflows/claude-dedupe-issues.yml
@@ -23,14 +23,81 @@ jobs:
uses: actions/checkout@v4
- name: Run Claude Code slash command
+ id: claude
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_args: "--model claude-sonnet-4-5-20250929"
+ # Note: GH_TOKEN only provides read access for issue viewing/searching
+ # Comment posting is handled in a separate isolated step below
claude_env: |
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
+ # SECURITY: This step runs in isolation without access to ANTHROPIC_API_KEY
+ # It only has GITHUB_TOKEN for posting comments, preventing secret exfiltration
+ - name: Post duplicate comment (isolated from API key)
+ if: success()
+ env:
+ GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
+ run: |
+ ISSUE_NUMBER=${{ github.event.issue.number || inputs.issue_number }}
+ RESULT_FILE="/tmp/dedupe-result.json"
+
+ if [ ! -f "$RESULT_FILE" ]; then
+ echo "No dedupe result file found, skipping comment"
+ exit 0
+ fi
+
+ # Check if we should skip
+ if jq -e '.skip' "$RESULT_FILE" > /dev/null 2>&1; then
+ REASON=$(jq -r '.reason // "unknown"' "$RESULT_FILE")
+ echo "Skipping comment: $REASON"
+ exit 0
+ fi
+
+ # Get duplicates array
+ DUPLICATES=$(jq -r '.duplicates // []' "$RESULT_FILE")
+ COUNT=$(echo "$DUPLICATES" | jq 'length')
+
+ if [ "$COUNT" -eq 0 ]; then
+ echo "No duplicates found, skipping comment"
+ exit 0
+ fi
+
+ # Build comment body (limit to 3 duplicates for safety)
+ SAFE_COUNT=$((COUNT > 3 ? 3 : COUNT))
+
+ COMMENT="Found $SAFE_COUNT possible duplicate issue"
+ if [ "$SAFE_COUNT" -ne 1 ]; then
+ COMMENT="${COMMENT}s"
+ fi
+ COMMENT="${COMMENT}:"
+ COMMENT="${COMMENT}
+ "
+
+ for i in $(seq 0 $((SAFE_COUNT - 1))); do
+ URL=$(echo "$DUPLICATES" | jq -r ".[$i]")
+ # Validate URL format to prevent injection
+ if [[ "$URL" =~ ^https://github\.com/[a-zA-Z0-9_.-]+/[a-zA-Z0-9_.-]+/issues/[0-9]+$ ]]; then
+ COMMENT="${COMMENT}
+ $((i + 1)). $URL"
+ fi
+ done
+
+ COMMENT="${COMMENT}
+
+ 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)"
+
+ # Post the comment
+ gh issue comment "$ISSUE_NUMBER" --repo "${{ github.repository }}" --body "$COMMENT"
+ echo "Posted duplicate comment on issue #$ISSUE_NUMBER"
+
- name: Log duplicate comment event to Statsig
if: always()
env: