surshar / claude-code
Claude Code is an agentic coding tool that lives in your terminal, understands your codebase, and helps you code faster by executing routine tasks, explaining complex code, and handling git workflows - all through natural language commands.
Built by Anthropic · github.com/anthropics
| M.claude/commands/dedupe.md |
| M.claude/commands/triage-issue.md |
| M.github/workflows/claude-issue-triage.yml |
| Ascripts/gh.sh |
| @@ -1,5 +1,5 @@ | ||
| 1 | 1 | --- |
| 2 | -allowed-tools: Bash(gh issue view:*), Bash(gh search:*), Bash(gh issue list:*), Bash(./scripts/comment-on-duplicates.sh:*) | |
| 2 | +allowed-tools: Bash(./scripts/gh.sh:*), Bash(./scripts/comment-on-duplicates.sh:*) | |
| 3 | 3 | description: Find duplicate GitHub issues |
| 4 | 4 | --- |
| 5 | 5 | |
| @@ -18,6 +18,10 @@ | ||
| 18 | 18 | |
| 19 | 19 | Notes (be sure to tell this to your agents, too): |
| 20 | 20 | |
| 21 | -- Use `gh` to interact with Github, rather than web fetch | |
| 22 | -- Do not use other tools, beyond `gh` and the comment script (eg. don't use other MCP servers, file edit, etc.) | |
| 21 | +- Use `./scripts/gh.sh` to interact with Github, rather than web fetch or raw `gh`. Examples: | |
| 22 | + - `./scripts/gh.sh issue view 123` — view an issue | |
| 23 | + - `./scripts/gh.sh issue view 123 --comments` — view with comments | |
| 24 | + - `./scripts/gh.sh issue list --state open --limit 20` — list issues | |
| 25 | + - `./scripts/gh.sh search issues "query" --limit 10` — search for issues | |
| 26 | +- Do not use other tools, beyond `./scripts/gh.sh` and the comment script (eg. don't use other MCP servers, file edit, etc.) | |
| 23 | 27 | - Make a todo list first |
| @@ -1,5 +1,5 @@ | ||
| 1 | 1 | --- |
| 2 | -allowed-tools: Bash(gh label list:*),Bash(gh issue view:*),Bash(./scripts/edit-issue-labels.sh:*),Bash(gh search issues:*) | |
| 2 | +allowed-tools: Bash(./scripts/gh.sh:*),Bash(./scripts/edit-issue-labels.sh:*) | |
| 3 | 3 | description: Triage GitHub issues by analyzing and applying labels |
| 4 | 4 | --- |
| 5 | 5 | |
| @@ -12,17 +12,21 @@ | ||
| 12 | 12 | $ARGUMENTS |
| 13 | 13 | |
| 14 | 14 | TOOLS: |
| 15 | -- `gh label list`: Fetch all available labels in this repo | |
| 16 | -- `gh issue view NUMBER`: Read the issue title, body, and labels | |
| 17 | -- `gh issue view NUMBER --comments`: Read the conversation | |
| 18 | -- `gh search issues QUERY`: Find similar or duplicate issues | |
| 19 | -- `./scripts/edit-issue-labels.sh --issue NUMBER --add-label LABEL --remove-label LABEL`: Add or remove labels | |
| 15 | +- `./scripts/gh.sh` — wrapper for `gh` CLI. Only supports these subcommands and flags: | |
| 16 | + - `./scripts/gh.sh label list` — fetch all available labels | |
| 17 | + - `./scripts/gh.sh label list --limit 100` — fetch with limit | |
| 18 | + - `./scripts/gh.sh issue view 123` — read issue title, body, and labels | |
| 19 | + - `./scripts/gh.sh issue view 123 --comments` — read the conversation | |
| 20 | + - `./scripts/gh.sh issue list --state open --limit 20` — list issues | |
| 21 | + - `./scripts/gh.sh search issues "query"` — find similar or duplicate issues | |
| 22 | + - `./scripts/gh.sh search issues "query" --limit 10` — search with limit | |
| 23 | +- `./scripts/edit-issue-labels.sh --issue NUMBER --add-label LABEL --remove-label LABEL` — add or remove labels | |
| 20 | 24 | |
| 21 | 25 | TASK: |
| 22 | 26 | |
| 23 | -1. Run `gh label list` to fetch the available labels. You may ONLY use labels from this list. Never invent new labels. | |
| 24 | -2. Run `gh issue view ISSUE_NUMBER` to read the issue details. | |
| 25 | -3. Run `gh issue view ISSUE_NUMBER --comments` to read the conversation. | |
| 27 | +1. Run `./scripts/gh.sh label list` to fetch the available labels. You may ONLY use labels from this list. Never invent new labels. | |
| 28 | +2. Run `./scripts/gh.sh issue view ISSUE_NUMBER` to read the issue details. | |
| 29 | +3. Run `./scripts/gh.sh issue view ISSUE_NUMBER --comments` to read the conversation. | |
| 26 | 30 | |
| 27 | 31 | **If EVENT is "issues" (new issue):** |
| 28 | 32 | |
| @@ -31,7 +35,7 @@ | ||
| 31 | 35 | 5. Analyze and apply category labels: |
| 32 | 36 | - Type (bug, enhancement, question, etc.) |
| 33 | 37 | - Technical areas and platform |
| 34 | - - Check for duplicates with `gh search issues`. Only mark as duplicate of OPEN issues. | |
| 38 | + - Check for duplicates with `./scripts/gh.sh search issues`. Only mark as duplicate of OPEN issues. | |
| 35 | 39 | |
| 36 | 40 | 6. Evaluate lifecycle labels: |
| 37 | 41 | - `needs-repro` (bugs only, 7 days): Bug reports without clear steps to reproduce. A good repro has specific, followable steps that someone else could use to see the same issue. |
| @@ -58,7 +62,7 @@ | ||
| 58 | 62 | - Do NOT add or remove category labels (bug, enhancement, etc.) on comment events. |
| 59 | 63 | |
| 60 | 64 | GUIDELINES: |
| 61 | -- ONLY use labels from `gh label list` — never create or guess label names | |
| 65 | +- ONLY use labels from `./scripts/gh.sh label list` — never create or guess label names | |
| 62 | 66 | - DO NOT post any comments to the issue |
| 63 | 67 | - Be conservative with lifecycle labels — only apply when clearly warranted |
| 64 | 68 | - Only apply lifecycle labels (`needs-repro`, `needs-info`) to bugs — never to questions or enhancements |
| @@ -29,6 +29,7 @@ | ||
| 29 | 29 | uses: anthropics/claude-code-action@v1 |
| 30 | 30 | env: |
| 31 | 31 | GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} |
| 32 | + GH_REPO: ${{ github.repository }} | |
| 32 | 33 | with: |
| 33 | 34 | github_token: ${{ secrets.GITHUB_TOKEN }} |
| 34 | 35 | allowed_non_write_users: "*" |
| @@ -1,0 +1,85 @@ | ||
| 1 | +#!/usr/bin/env bash | |
| 2 | +set -euo pipefail | |
| 3 | + | |
| 4 | +# Wrapper around gh CLI that only allows specific subcommands and flags. | |
| 5 | +# All commands are scoped to the current repository via GH_REPO or GITHUB_REPOSITORY. | |
| 6 | +# | |
| 7 | +# Usage: | |
| 8 | +# ./scripts/gh.sh issue view 123 | |
| 9 | +# ./scripts/gh.sh issue view 123 --comments | |
| 10 | +# ./scripts/gh.sh issue list --state open --limit 20 | |
| 11 | +# ./scripts/gh.sh search issues "search query" --limit 10 | |
| 12 | +# ./scripts/gh.sh label list --limit 100 | |
| 13 | + | |
| 14 | +ALLOWED_FLAGS=(--comments --state --limit --label) | |
| 15 | +FLAGS_WITH_VALUES=(--state --limit --label) | |
| 16 | + | |
| 17 | +SUB1="${1:-}" | |
| 18 | +SUB2="${2:-}" | |
| 19 | +CMD="$SUB1 $SUB2" | |
| 20 | +case "$CMD" in | |
| 21 | + "issue view"|"issue list"|"search issues"|"label list") | |
| 22 | + ;; | |
| 23 | + *) | |
| 24 | + exit 1 | |
| 25 | + ;; | |
| 26 | +esac | |
| 27 | + | |
| 28 | +shift 2 | |
| 29 | + | |
| 30 | +# Separate flags from positional arguments | |
| 31 | +POSITIONAL=() | |
| 32 | +FLAGS=() | |
| 33 | +skip_next=false | |
| 34 | +for arg in "$@"; do | |
| 35 | + if [[ "$skip_next" == true ]]; then | |
| 36 | + FLAGS+=("$arg") | |
| 37 | + skip_next=false | |
| 38 | + elif [[ "$arg" == -* ]]; then | |
| 39 | + flag="${arg%%=*}" | |
| 40 | + matched=false | |
| 41 | + for allowed in "${ALLOWED_FLAGS[@]}"; do | |
| 42 | + if [[ "$flag" == "$allowed" ]]; then | |
| 43 | + matched=true | |
| 44 | + break | |
| 45 | + fi | |
| 46 | + done | |
| 47 | + if [[ "$matched" == false ]]; then | |
| 48 | + exit 1 | |
| 49 | + fi | |
| 50 | + FLAGS+=("$arg") | |
| 51 | + # If flag expects a value and isn't using = syntax, skip next arg | |
| 52 | + if [[ "$arg" != *=* ]]; then | |
| 53 | + for vflag in "${FLAGS_WITH_VALUES[@]}"; do | |
| 54 | + if [[ "$flag" == "$vflag" ]]; then | |
| 55 | + skip_next=true | |
| 56 | + break | |
| 57 | + fi | |
| 58 | + done | |
| 59 | + fi | |
| 60 | + else | |
| 61 | + POSITIONAL+=("$arg") | |
| 62 | + fi | |
| 63 | +done | |
| 64 | + | |
| 65 | +REPO="${GH_REPO:-${GITHUB_REPOSITORY:-}}" | |
| 66 | + | |
| 67 | +if [[ "$CMD" == "search issues" ]]; then | |
| 68 | + if [[ -z "$REPO" ]]; then | |
| 69 | + exit 1 | |
| 70 | + fi | |
| 71 | + QUERY="${POSITIONAL[0]:-}" | |
| 72 | + QUERY_LOWER=$(echo "$QUERY" | tr '[:upper:]' '[:lower:]') | |
| 73 | + if [[ "$QUERY_LOWER" == *"repo:"* || "$QUERY_LOWER" == *"org:"* || "$QUERY_LOWER" == *"user:"* ]]; then | |
| 74 | + exit 1 | |
| 75 | + fi | |
| 76 | + gh "$SUB1" "$SUB2" "$QUERY" --repo "$REPO" "${FLAGS[@]}" | |
| 77 | +else | |
| 78 | + # Reject URLs in positional args to prevent cross-repo access | |
| 79 | + for pos in "${POSITIONAL[@]}"; do | |
| 80 | + if [[ "$pos" == http://* || "$pos" == https://* ]]; then | |
| 81 | + exit 1 | |
| 82 | + fi | |
| 83 | + done | |
| 84 | + gh "$SUB1" "$SUB2" "${POSITIONAL[@]}" "${FLAGS[@]}" | |
| 85 | +fi |