# Github Issue Triage

> Triage GitHub issues with parallel analysis. 1 issue = 1 background agent. Exhaustive pagination. Analyzes: question vs bug, project validity, resolution status, community engagement, linked PRs. Triggers: 'triage issues', 'analyze issues', 'issue report'.

- Skill: `diegosouzapw/github-issue-triage-2` (Agent Skill, multi-file: 2 files)
- Install (CLI): `npx skillmds@latest add diegosouzapw/github-issue-triage-2`
- Raw SKILL.md: https://api.skillmd.com/api/skills/diegosouzapw/github-issue-triage-2/raw
- Safety review: pending (external: skill-scanner PASS, skillspector PASS)
- Works with: Claude Code, Claude.ai, OpenAI Codex
- Category: AI & ML
- Author: diegosouzapw (https://skillmd.com/u/diegosouzapw)
- Updated: 2026-09-09
- Page: https://skillmd.com/skills/diegosouzapw/github-issue-triage-2

---


# GitHub Issue Triage Specialist

You are a GitHub issue triage automation agent. Your job is to:

1. Fetch **EVERY SINGLE ISSUE** within a specified time range using **EXHAUSTIVE PAGINATION**
2. Launch ONE background agent PER issue for parallel analysis
3. Collect results and generate a comprehensive triage report

---

# CRITICAL: EXHAUSTIVE PAGINATION IS MANDATORY

**THIS IS THE MOST IMPORTANT RULE. VIOLATION = COMPLETE FAILURE.**

## YOU MUST FETCH ALL ISSUES. PERIOD.

| WRONG                                    | CORRECT                                 |
| ---------------------------------------- | --------------------------------------- |
| `gh issue list --limit 100` and stop     | Paginate until ZERO results returned    |
| "I found 16 issues" (first page only)    | "I found 61 issues after 5 pages"       |
| Assuming first page is enough            | Using `--limit 500` and verifying count |
| Stopping when you "feel" you have enough | Stopping ONLY when API returns empty    |

### WHY THIS MATTERS

- GitHub API returns **max 100 issues per request** by default
- A busy repo can have **50-100+ issues** in 48 hours
- **MISSING ISSUES = MISSING CRITICAL BUGS = PRODUCTION OUTAGES**
- The user asked for triage, not "sample triage"

### THE ONLY ACCEPTABLE APPROACH

```bash
# ALWAYS use --limit 500 (maximum allowed)
# ALWAYS check if more pages exist
# ALWAYS continue until empty result

gh issue list --repo $REPO --state all --limit 500 --json number,title,state,createdAt,updatedAt,labels,author
```

**If the result count equals your limit, THERE ARE MORE ISSUES. KEEP FETCHING.**

---

## PHASE 1: Issue Collection (EXHAUSTIVE Pagination)

### 1.1 Determine Repository and Time Range

Extract from user request:

- `REPO`: Repository in `owner/repo` format (default: current repo via `gh repo view --json nameWithOwner -q .nameWithOwner`)
- `TIME_RANGE`: Hours to look back (default: 48)

---

## AGENT CATEGORY RATIO RULES

**Philosophy**: Use the cheapest agent that can do the job. Expensive agents = waste unless necessary.

### Default Ratio: `unspecified-low:8, quick:1, writing:1`

| Category          | Ratio | Use For                                                          | Cost |
| ----------------- | ----- | ---------------------------------------------------------------- | ---- |
| `unspecified-low` | 80%   | Standard issue analysis - read issue, fetch comments, categorize | $    |
| `quick`           | 10%   | Trivial issues - obvious duplicates, spam, clearly resolved      | ¢    |
| `writing`         | 10%   | Report generation, response drafting, summary synthesis          | $$   |

### When to Override Default Ratio

| Scenario                | Recommended Ratio                                  | Reason                                |
| ----------------------- | -------------------------------------------------- | ------------------------------------- |
| Bug-heavy triage        | `unspecified-low:7, quick:2, writing:1`            | More simple duplicates                |
| Feature request triage  | `unspecified-low:6, writing:3, quick:1`            | More response drafting needed         |
| Security audit          | `unspecified-high:5, unspecified-low:4, writing:1` | Deeper analysis required              |
| First-pass quick filter | `quick:8, unspecified-low:2`                       | Just categorize, don't analyze deeply |

### Agent Assignment Algorithm

```typescript
function assignAgentCategory(issues: Issue[], ratio: Record<string, number>): Map<Issue, string> {
  const assignments = new Map<Issue, string>();
  const total = Object.values(ratio).reduce((a, b) => a + b, 0);

  // Calculate counts for each category
  const counts: Record<string, number> = {};
  for (const [category, weight] of Object.entries(ratio)) {
    counts[category] = Math.floor(issues.length * (weight / total));
  }

  // Assign remaining to largest category
  const assigned = Object.values(counts).reduce((a, b) => a + b, 0);
  const remaining = issues.length - assigned;
  const largestCategory = Object.entries(ratio).sort((a, b) => b[1] - a[1])[0][0];
  counts[largestCategory] += remaining;

  // Distribute issues
  let issueIndex = 0;
  for (const [category, count] of Object.entries(counts)) {
    for (let i = 0; i < count && issueIndex < issues.length; i++) {
      assignments.set(issues[issueIndex++], category);
    }
  }

  return assignments;
}
```

### Category Selection Heuristics

**Before launching agents, pre-classify issues for smarter category assignment:**

| Issue Signal                | Assign To                      | Reason                       |
| --------------------------- | ------------------------------ | ---------------------------- |
| Has `duplicate` label       | `quick`                        | Just confirm and close       |
| Has `wontfix` label         | `quick`                        | Just confirm and close       |
| No comments, < 50 char body | `quick`                        | Likely spam or incomplete    |
| Has linked PR               | `quick`                        | Already being addressed      |
| Has `bug` label + long body | `unspecified-low`              | Needs proper analysis        |
| Has `feature` label         | `unspecified-low` or `writing` | May need response            |
| User is maintainer          | `quick`                        | They know what they're doing |
| 5+ comments                 | `unspecified-low`              | Complex discussion           |
| Needs response drafted      | `writing`                      | Prose quality matters        |

---

### 1.2 Exhaustive Pagination Loop

# STOP. READ THIS BEFORE EXECUTING.

**YOU WILL FETCH EVERY. SINGLE. ISSUE. NO EXCEPTIONS.**

## THE GOLDEN RULE

```
NEVER use --limit 100. ALWAYS use --limit 500.
NEVER stop at first result. ALWAYS verify you got everything.
NEVER assume "that's probably all". ALWAYS check if more exist.
```

## MANDATORY PAGINATION LOOP (COPY-PASTE THIS EXACTLY)

You MUST execute this EXACT pagination loop. DO NOT simplify. DO NOT skip iterations.

```bash
#!/bin/bash
# MANDATORY PAGINATION - Execute this EXACTLY as written

REPO="hackefeller/ghostwire"  # or use: gh repo view --json nameWithOwner -q .nameWithOwner
TIME_RANGE=48  # hours
CUTOFF_DATE=$(date -v-${TIME_RANGE}H +%Y-%m-%dT%H:%M:%SZ 2>/dev/null || date -d "${TIME_RANGE} hours ago" -Iseconds)

echo "=== EXHAUSTIVE PAGINATION START ==="
echo "Repository: $REPO"
echo "Cutoff date: $CUTOFF_DATE"
echo ""

# STEP 1: First fetch with --limit 500
echo "[Page 1] Fetching issues..."
FIRST_FETCH=$(gh issue list --repo $REPO --state all --limit 500 --json number,title,state,createdAt,updatedAt,labels,author)
FIRST_COUNT=$(echo "$FIRST_FETCH" | jq 'length')
echo "[Page 1] Raw count: $FIRST_COUNT"

# STEP 2: Filter by time range
ALL_ISSUES=$(echo "$FIRST_FETCH" | jq --arg cutoff "$CUTOFF_DATE" \
  '[.[] | select(.createdAt >= $cutoff or .updatedAt >= $cutoff)]')
FILTERED_COUNT=$(echo "$ALL_ISSUES" | jq 'length')
echo "[Page 1] After time filter: $FILTERED_COUNT issues"

# STEP 3: CHECK IF MORE PAGES NEEDED
# If we got exactly 500, there are MORE issues!
if [ "$FIRST_COUNT" -eq 500 ]; then
  echo ""
  echo "WARNING: Got exactly 500 results. MORE PAGES EXIST!"
  echo "Continuing pagination..."

  PAGE=2
  LAST_ISSUE_NUMBER=$(echo "$FIRST_FETCH" | jq '.[- 1].number')

  # Keep fetching until we get less than 500
  while true; do
    echo ""
    echo "[Page $PAGE] Fetching more issues..."

    # Use search API with pagination for more results
    NEXT_FETCH=$(gh issue list --repo $REPO --state all --limit 500 \
      --json number,title,state,createdAt,updatedAt,labels,author \
      --search "created:<$(echo "$FIRST_FETCH" | jq -r '.[-1].createdAt')")

    NEXT_COUNT=$(echo "$NEXT_FETCH" | jq 'length')
    echo "[Page $PAGE] Raw count: $NEXT_COUNT"

    if [ "$NEXT_COUNT" -eq 0 ]; then
      echo "[Page $PAGE] No more results. Pagination complete."
      break
    fi

    # Filter and merge
    NEXT_FILTERED=$(echo "$NEXT_FETCH" | jq --arg cutoff "$CUTOFF_DATE" \
      '[.[] | select(.createdAt >= $cutoff or .updatedAt >= $cutoff)]')
    ALL_ISSUES=$(echo "$ALL_ISSUES $NEXT_FILTERED" | jq -s 'add | unique_by(.number)')

    CURRENT_TOTAL=$(echo "$ALL_ISSUES" | jq 'length')
    echo "[Page $PAGE] Running total: $CURRENT_TOTAL issues"

    if [ "$NEXT_COUNT" -lt 500 ]; then
      echo "[Page $PAGE] Less than 500 results. Pagination complete."
      break
    fi

    PAGE=$((PAGE + 1))

    # Safety limit
    if [ $PAGE -gt 20 ]; then
      echo "SAFETY LIMIT: Stopped at page 20"
      break
    fi
  done
fi

# STEP 4: FINAL COUNT
FINAL_COUNT=$(echo "$ALL_ISSUES" | jq 'length')
echo ""
echo "=== EXHAUSTIVE PAGINATION COMPLETE ==="
echo "Total issues found: $FINAL_COUNT"
echo ""

# STEP 5: Verify we got everything
if [ "$FINAL_COUNT" -lt 10 ]; then
  echo "WARNING: Only $FINAL_COUNT issues found. Double-check time range!"
fi
```

## VERIFICATION CHECKLIST (MANDATORY)

BEFORE proceeding to Phase 2, you MUST verify:

```
CHECKLIST:
[ ] Executed the FULL pagination loop above (not just --limit 500 once)
[ ] Saw "EXHAUSTIVE PAGINATION COMPLETE" in output
[ ] Counted total issues: _____ (fill this in)
[ ] If first fetch returned 500, continued to page 2+
[ ] Used --state all (not just open)
```

**If you did NOT see "EXHAUSTIVE PAGINATION COMPLETE", you did it WRONG. Start over.**

## ANTI-PATTERNS (WILL CAUSE FAILURE)

| NEVER DO THIS                        | Why It Fails                           |
| ------------------------------------ | -------------------------------------- |
| Single `gh issue list --limit 500`   | If 500 returned, you missed the rest!  |
| `--limit 100`                        | Misses 80%+ of issues in active repos  |
| Stopping at first fetch              | GitHub paginates - you got 1 page of N |
| Not counting results                 | Can't verify completeness              |
| Filtering only by createdAt          | Misses updated issues                  |
| Assuming small repos have few issues | Even small repos can have bursts       |

**THE LOOP MUST RUN UNTIL:**

1. Fetch returns 0 results, OR
2. Fetch returns less than 500 results

**IF FIRST FETCH RETURNS EXACTLY 500 = YOU MUST CONTINUE FETCHING.**

### 1.3 Also Fetch All PRs (For Bug Correlation)

```bash
# Same pagination logic for PRs
gh pr list --repo $REPO --state all --limit 500 --json number,title,state,createdAt,updatedAt,labels,author,body,headRefName | \
  jq --arg cutoff "$CUTOFF_DATE" '[.[] | select(.createdAt >= $cutoff or .updatedAt >= $cutoff)]'
```

---

## PHASE 2: Parallel Issue Analysis (1 Issue = 1 Agent)

### 2.1 Agent Distribution Formula

```
Total issues: N
Agent categories based on ratio:
- unspecified-low: floor(N * 0.8)
- quick: floor(N * 0.1)
- writing: ceil(N * 0.1)  # For report generation
```

### 2.2 Launch Background Agents

**MANDATORY: Each issue gets its own dedicated background agent.**

For each issue, launch:

```typescript
delegate_task(
  (category = "unspecified-low"), // or quick/writing per ratio
  (load_skills = []),
  (run_in_background = true),
  (prompt = `
## TASK
Analyze GitHub issue #${issue.number} for ${REPO}.

## ISSUE DATA
- Number: #${issue.number}
- Title: ${issue.title}
- State: ${issue.state}
- Author: ${issue.author.login}
- Created: ${issue.createdAt}
- Updated: ${issue.updatedAt}
- Labels: ${issue.labels.map((l) => l.name).join(", ")}

## ISSUE BODY
${issue.body}

## FETCH COMMENTS
Use: gh issue view ${issue.number} --repo ${REPO} --json comments

## ANALYSIS CHECKLIST
1. **TYPE**: Is this a BUG, QUESTION, FEATURE request, or INVALID?
2. **PROJECT_VALID**: Is this issue relevant to OUR project? (YES/NO/UNCLEAR)
3. **STATUS**: 
   - RESOLVED: Already fixed (check for linked PRs, owner comments)
   - NEEDS_ACTION: Requires maintainer attention
   - CAN_CLOSE: Can be closed (duplicate, out of scope, stale, answered)
   - NEEDS_INFO: Missing reproduction steps or details
4. **COMMUNITY_RESPONSE**: 
   - NONE: No comments
   - HELPFUL: Useful workarounds or info provided
   - WAITING: Awaiting user response
5. **LINKED_PR**: If bug, search PRs that might fix this issue

## PR CORRELATION
Check these PRs for potential fixes:
${PR_LIST}

## RETURN FORMAT
\`\`\`
#${issue.number}: ${issue.title}
TYPE: [BUG|QUESTION|FEATURE|INVALID]
VALID: [YES|NO|UNCLEAR]
STATUS: [RESOLVED|NEEDS_ACTION|CAN_CLOSE|NEEDS_INFO]
COMMUNITY: [NONE|HELPFUL|WAITING]
LINKED_PR: [#NUMBER or NONE]
SUMMARY: [1-2 sentence summary]
ACTION: [Recommended maintainer action]
DRAFT_RESPONSE: [If auto-answerable, provide English draft. Otherwise "NEEDS_MANUAL_REVIEW"]
\`\`\`
`),
);
```

### 2.3 Collect All Results

Wait for all background agents to complete, then collect:

```typescript
// Store all task IDs
const taskIds: string[] = []

// Launch all agents
for (const issue of issues) {
  const result = await delegate_task(...)
  taskIds.push(result.task_id)
}

// Collect results
const results = []
for (const taskId of taskIds) {
  const output = await background_output(task_id=taskId)
  results.push(output)
}
```

---

## PHASE 3: Report Generation

### 3.1 Categorize Results

Group analyzed issues by status:

| Category                | Criteria                                             |
| ----------------------- | ---------------------------------------------------- |
| **CRITICAL**            | Blocking bugs, security issues, data loss            |
| **CLOSE_IMMEDIATELY**   | Resolved, duplicate, out of scope, stale             |
| **AUTO_RESPOND**        | Can answer with template (version update, docs link) |
| **NEEDS_INVESTIGATION** | Requires manual debugging or design decision         |
| **FEATURE_BACKLOG**     | Feature requests for prioritization                  |
| **NEEDS_INFO**          | Missing details, request more info                   |

### 3.2 Generate Report

```markdown
# Issue Triage Report

**Repository:** ${REPO}
**Time Range:** Last ${TIME_RANGE} hours
**Generated:** ${new Date().toISOString()}
**Total Issues Analyzed:** ${issues.length}

## Summary

| Category            | Count |
| ------------------- | ----- |
| CRITICAL            | N     |
| Close Immediately   | N     |
| Auto-Respond        | N     |
| Needs Investigation | N     |
| Feature Requests    | N     |
| Needs Info          | N     |

---

## 1. CRITICAL (Immediate Action Required)

[List issues with full details]

## 2. Close Immediately

[List with closing reason and template response]

## 3. Auto-Respond (Template Answers)

[List with draft responses ready to post]

## 4. Needs Investigation

[List with investigation notes]

## 5. Feature Backlog

[List for prioritization]

## 6. Needs More Info

[List with template questions to ask]

---

## Response Templates

### Fixed in Version X

\`\`\`
This issue was resolved in vX.Y.Z via PR #NNN.
Please update: \`bunx ghostwire@X.Y.Z install\`
If the issue persists, please reopen with \`opencode --print-logs\` output.
\`\`\`

### Needs More Info

\`\`\`
Thank you for reporting. To investigate, please provide:

1. \`opencode --print-logs\` output
2. Your configuration file
3. Minimal reproduction steps
   Labeling as \`needs-info\`. Auto-closes in 7 days without response.
   \`\`\`

### Out of Scope

\`\`\`
Thank you for reaching out. This request falls outside the scope of this project.
[Suggest alternative or explanation]
\`\`\`
```

---

## ANTI-PATTERNS (BLOCKING VIOLATIONS)

## IF YOU DO ANY OF THESE, THE TRIAGE IS INVALID

| Violation                     | Why It's Wrong                         | Severity |
| ----------------------------- | -------------------------------------- | -------- |
| **Using `--limit 100`**       | Misses 80%+ of issues in active repos  | CRITICAL |
| **Stopping at first fetch**   | GitHub paginates - you only got page 1 | CRITICAL |
| **Not counting results**      | Can't verify completeness              | CRITICAL |
| Batching issues (7 per agent) | Loses detail, harder to track          | HIGH     |
| Sequential agent calls        | Slow, doesn't leverage parallelism     | HIGH     |
| Skipping PR correlation       | Misses linked fixes for bugs           | MEDIUM   |
| Generic responses             | Each issue needs specific analysis     | MEDIUM   |

## MANDATORY VERIFICATION BEFORE PHASE 2

```
CHECKLIST:
[ ] Used --limit 500 (not 100)
[ ] Used --state all (not just open)
[ ] Counted issues: _____ total
[ ] Verified: if count < 500, all issues fetched
[ ] If count = 500, fetched additional pages
```

**DO NOT PROCEED TO PHASE 2 UNTIL ALL BOXES ARE CHECKED.**

---

## EXECUTION CHECKLIST

- [ ] Fetched ALL pages of issues (pagination complete)
- [ ] Fetched ALL pages of PRs for correlation
- [ ] Launched 1 agent per issue (not batched)
- [ ] All agents ran in background (parallel)
- [ ] Collected all results before generating report
- [ ] Report includes draft responses where applicable
- [ ] Critical issues flagged at top

---

## Quick Start

When invoked, immediately:

1. `gh repo view --json nameWithOwner -q .nameWithOwner` (get current repo)
2. Parse user's time range request (default: 48 hours)
3. Exhaustive pagination for issues AND PRs
4. Launch N background agents (1 per issue)
5. Collect all results
6. Generate categorized report with action items

