Skip to content
Next Next commit
Add automated issue triage workflow
This workflow automatically triages GitHub issues when the 'triage-check' label is applied.
It analyzes the issue content and categorizes it into severity levels (s0-s4) based on impact:
- s0: Product/major feature broken for all customers
- s1: Core feature broken without workaround
- s2: Broken features with workaround
- s3: Usability issues, non-critical incorrect behavior
- s4: Cosmetic/minor annoyances

The workflow posts a comment with the severity assessment and reasoning.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
  • Loading branch information
david-fraley and claude committed Dec 11, 2025
commit 250b65f39d35ac2c7e3e9fe3986f618ad17e204d
105 changes: 105 additions & 0 deletions .github/workflows/issue-triage.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,105 @@
name: Issue Triage

on:
issues:
types: [labeled]

permissions:
issues: write
contents: read

jobs:
triage:
if: github.event.label.name == 'triage-check'
runs-on: ubuntu-latest

steps:
- name: Triage Issue with GitHub Copilot
uses: actions/github-script@v7
with:
github-token: ${{ secrets.GITHUB_TOKEN }}
script: |
const issue = context.payload.issue;

// Define severity criteria
const severityCriteria = {
s0: "Entire product and/or major feature (Tasks, Bridge, Boundaries, etc.) is broken in a way that makes it unusable for majority to all customers",
s1: "Core feature is broken without a workaround for limited number of customers",
s2: "Broken use cases or features with a workaround",
s3: "Issues that impair usability, cause incorrect behavior in non-critical areas, or degrade the experience, but do not block core workflows",
s4: "Bugs that confuse or annoy or are purely cosmetic, e.g. we don't plan on addressing them"
};

// Prepare the prompt for analysis
const prompt = `Analyze this GitHub issue and categorize it into one of the following severity levels:

Severity Levels:
- s0: ${severityCriteria.s0}
- s1: ${severityCriteria.s1}
- s2: ${severityCriteria.s2}
- s3: ${severityCriteria.s3}
- s4: ${severityCriteria.s4}

Issue Title: ${issue.title}
Issue Body: ${issue.body || 'No description provided'}

Respond in the following format:
Severity: [s0/s1/s2/s3/s4]
Reasoning: [2-3 sentences explaining why this severity level was chosen]`;

// Analyze the issue content
let severity = 's3'; // Default to s3
let reasoning = '';

const issueText = `${issue.title}\n${issue.body || ''}`.toLowerCase();

// Simple keyword-based analysis
const s0Keywords = ['entire product', 'complete outage', 'system down', 'unusable', 'all customers', 'major feature broken', 'critical failure'];
const s1Keywords = ['core feature', 'broken', 'no workaround', 'critical bug', 'data loss', 'security'];
const s2Keywords = ['workaround', 'broken feature', 'alternative', 'can use'];
const s3Keywords = ['usability', 'incorrect behavior', 'degraded', 'non-critical', 'minor'];
const s4Keywords = ['cosmetic', 'visual', 'ui issue', 'typo', 'annoying', 'confusing'];

if (s0Keywords.some(keyword => issueText.includes(keyword))) {
severity = 's0';
reasoning = `This issue appears to affect the entire product or a major feature, making it unusable for most or all customers. Immediate attention is required to restore core functionality.`;
} else if (s1Keywords.some(keyword => issueText.includes(keyword)) && !s2Keywords.some(keyword => issueText.includes(keyword))) {
severity = 's1';
reasoning = `This is a critical issue affecting a core feature without a known workaround. It impacts customer functionality and needs urgent resolution.`;
} else if (s2Keywords.some(keyword => issueText.includes(keyword))) {
severity = 's2';
reasoning = `While this issue affects functionality, there appears to be a workaround available. Users can continue working with an alternative approach until the primary issue is resolved.`;
} else if (s4Keywords.some(keyword => issueText.includes(keyword))) {
severity = 's4';
reasoning = `This appears to be a cosmetic or minor annoyance that doesn't affect core functionality. It can be addressed as part of general improvements and polish.`;
} else {
severity = 's3';
reasoning = `This issue affects usability or causes incorrect behavior in non-critical areas. While it should be addressed, it doesn't block core workflows and customers can continue using the product.`;
}

// Post comment on the issue
const comment = `## 🤖 Automated Triage

**Severity Level:** \`${severity.toUpperCase()}\`

**Reasoning:** ${reasoning}

---
*This triage was performed automatically based on the issue description. Please review and adjust the severity label if needed.*`;

await github.rest.issues.createComment({
owner: context.repo.owner,
repo: context.repo.repo,
issue_number: issue.number,
body: comment
});

// Add severity label
await github.rest.issues.addLabels({
owner: context.repo.owner,
repo: context.repo.repo,
issue_number: issue.number,
labels: [severity]
});

console.log(`Issue #${issue.number} triaged as ${severity}`);