Claude
Skills
Sign in
Back

issue-triage

Included with Lifetime
$97 forever

3-phase issue backlog management with audit, deep analysis, and validated triage actions. Use when triaging GitHub issues, sorting bug reports, cleaning up stale tickets, or detecting duplicate issues. Args: 'all' to analyze all, issue numbers to focus (e.g. '42 57'), 'en'/'fr' for language, no arg = audit only.

Security

What this skill does


# Issue Triage

3-phase workflow for maintainers: automated audit of all open issues, opt-in deep analysis via parallel agents, and validated triage actions (comments, labels, closures).

## When to Use This Skill

| Skill | Usage | Output |
|-------|-------|--------|
| `/issue-triage` | Sort, analyze, and act on an issue backlog | Triage tables + analysis + executed actions |
| `/pr-triage` | Sort, review, and comment on a PR backlog | Triage table + reviews + posted comments |

**Triggers**:
- Manually: `/issue-triage` or `/issue-triage all` or `/issue-triage 42 57`
- Proactively: when >10 open issues without label, or stale issues >30 days detected

---

## Language

- Check the argument passed to the skill
- If `en` or `english` → tables and summary in English
- If `fr`, `french`, or no argument → French (default)
- Note: GitHub comments and labels (Phase 3) are ALWAYS in English (international audience)

---

## Configuration

Thresholds used throughout the workflow. Edit to match your project:

| Parameter | Default | Description |
|-----------|---------|-------------|
| `staleness_days` | 30 | Days without activity before flagging as stale |
| `very_stale_days` | 90 | Days without activity before flagging as very stale |
| `jaccard_threshold` | 60% | Minimum Jaccard similarity to flag two issues as duplicates |
| `closed_compare_count` | 20 | Number of recent closed issues to compare for duplicate detection |
| `open_limit` | 100 | Maximum open issues to fetch and analyze |

---

## Preconditions

```bash
git rev-parse --is-inside-work-tree
gh auth status
```

If either fails, stop and explain what is missing.

---

## Phase 1: Audit (always executed)

### Data Gathering (parallel commands)

```bash
# Repo identity
gh repo view --json nameWithOwner -q .nameWithOwner

# Open issues (exclude PRs, limit 100)
gh issue list --state open --limit 100 \
  --json number,title,author,createdAt,updatedAt,labels,body,comments,assignees,milestone

# Recent closed issues (for duplicate detection)
gh issue list --state closed --limit 20 \
  --json number,title,body,labels,stateReason

# Open PRs (bodies for cross-reference detection)
gh pr list --state open --limit 50 --json number,title,body

# Collaborators (to distinguish reporter types)
gh api "repos/{owner}/{repo}/collaborators" --jq '.[].login'
```

**Collaborators fallback**: if `gh api .../collaborators` returns 403/404:
```bash
# Extract authors from last 10 merged PRs
gh pr list --state merged --limit 10 --json author --jq '.[].author.login' | sort -u
```
If still ambiguous, ask via `AskUserQuestion`.

**Note**: `comments` field in `gh issue list --json comments` returns the count, not content. For Phase 2, fetch full content separately: `gh issue view {num} --json comments`.

### Analysis Dimensions

Run all 6 dimensions for each open issue:

#### 1. Categorization

Classify each issue by reading `title` + first 200 chars of `body`:

| Category | Label | Criteria |
|----------|-------|----------|
| Bug | `bug` | Describes broken behavior, unexpected output, crash |
| Feature Request | `enhancement` | Asks for new functionality |
| Question / Support | `question` | User asking how something works |
| Documentation | `documentation` | Missing or incorrect docs |
| Out of Scope | `wontfix` | Clearly outside project boundaries |
| Unclear | `needs-info` | Body empty, too vague to categorize |

If body is empty → category is always `Unclear` (never assume).

#### 2. Cross-reference to PRs

Scan each open PR body for references to the issue number:
- Patterns: `fixes #N`, `closes #N`, `resolves #N`, `fix #N`, `close #N` (case-insensitive, `N` = issue number)
- Use regex locally on the `body` fields already fetched; do NOT make N additional API calls
- If found: flag issue as "PR-linked" with PR number

#### 3. Duplicate Detection via Jaccard Similarity

**Algorithm (self-contained, no external library)**:

For each open issue, compute Jaccard similarity against all other open issues AND the 20 most recent closed issues.

```
Step 1: Normalize title + first 300 chars of body:
  - Lowercase the full text
  - Strip category prefixes: "feat:", "fix:", "bug:", "chore:", "docs:", "test:", "refactor:"
  - Remove punctuation: .,!?;:'"()[]{}-_/\@#

Step 2: Tokenize:
  - Split on whitespace
  - Remove stop words: the a an is in on to for of and or with this that it can not no be
  - Remove tokens shorter than 3 characters

Step 3: Compute Jaccard:
  tokens_A = set of tokens from issue A
  tokens_B = set of tokens from issue B
  jaccard = |tokens_A ∩ tokens_B| / |tokens_A ∪ tokens_B|

Step 4: Flag:
  - If jaccard >= 0.60: mark as potential duplicate
  - Report: "Similar to #N (Jaccard: 0.72)"
  - Keep the OLDER issue as canonical; newer = duplicate candidate
```

Jaccard is computed at runtime using the fetched data; no API calls beyond Phase 1 gather.

#### 4. Risk Classification

Assign Red / Yellow / Green based on signals in title + body:

| Level | Color | Criteria |
|-------|-------|----------|
| Critical | Red | Security vulnerability, data loss, regression blocking users, crash in production |
| Needs Attention | Yellow | Missing validation, performance degradation, breaking change undocumented, Unclear with no response for >7 days |
| Normal | Green | Everything else |

#### 5. Staleness

| Status | Criterion |
|--------|-----------|
| Active | Updated within 30 days |
| Stale | No activity 30–90 days |
| Very Stale | No activity >90 days |

Use `updatedAt` field. Staleness does NOT depend on comments count; a commented-on issue with old `updatedAt` is still stale.

#### 6. Recommendations

One recommended action per issue:

| Situation | Action |
|-----------|--------|
| Category = Unclear, body empty | Comment requesting details |
| Jaccard >= 0.60 with known issue | Close as duplicate, link original |
| Very stale + no assignee | Comment requesting status, suggest close |
| Risk = Red | Pin to top of triage, escalate immediately |
| Category = OOS | Close with explanation |
| PR-linked | No action needed (tracked via PR) |
| Normal + labeled | No action needed |

### Output: Triage Tables

```
## Open Issues ({count})

### Critical: Immediate Attention (Risk: Red)
| # | Title | Category | Reporter | Days Open | Action |
|---|-------|----------|----------|-----------|--------|

### PR-Linked (tracked in open PRs)
| # | Title | Category | PR | Days Open |
|---|-------|----------|----|-----------|

### Active Issues
| # | Title | Category | Labels | Reporter | Days | Action |
|---|-------|----------|--------|----------|------|--------|

### Duplicate Candidates
| # | Title | Similar To | Jaccard | Action |
|---|-------|------------|---------|--------|

### Stale Issues
| # | Title | Category | Last Activity | Reporter | Action |
|---|-------|----------|---------------|----------|--------|

### Summary
- Total open: {N}
- Critical (Red): {count}
- PR-linked: {count}
- Duplicate candidates: {count}
- Stale (30–90d): {count}
- Very stale (>90d): {count}
- Unlabeled: {count}
- Recommended actions: {comment: N, label: N, close: N}
```

0 issues → display `No open issues.` and stop.

**Protection rules** (apply to all phases):
- Never close an issue authored by a collaborator without explicit user confirmation
- Never re-label an issue that already has labels (only add missing labels)
- If body is empty → always request details before any other action
- Never auto-close a Red issue without user confirmation

### Automatic Copy

After displaying the triage tables, copy to clipboard using platform-appropriate command:

```bash
UNAME=$(uname -s)
if [ "$UNAME" = "Darwin" ]; then
  pbcopy <<'EOF'
{full triage tables}
EOF
elif command -v xclip &>/dev/null; then
  echo "{full triage tables}" | xclip -selection clipboard
elif command -v wl-copy &>/dev/null; then
  echo "{full triage tables}" | wl-copy
elif command -v clip.exe &>/dev/null; then
  echo "{full triage tables}" | clip.exe
fi
```

Confirm: `Triage tables 

Related in Security