Issue Triage workflow (ruaan-deysel/ha-unraid)
The Issue Triage workflow from ruaan-deysel/ha-unraid, explained and optimized by Latchkey.
A
CI health: A - excellent
Point runs-on at Latchkey and get job timeouts, self-healing for flaky steps, and up to 58% lower cost, applied automatically.
What it does
This is the Issue Triage workflow from the ruaan-deysel/ha-unraid repository, a real project running GitHub Actions. It is shown here with attribution under its Apache-2.0 license.
Below, Latchkey shows a faster, safer version produced by its optimization engine.
The workflow
workflow (.yml)
name: Issue Triage
on:
issues:
types: [opened]
permissions:
issues: write
jobs:
triage:
runs-on: ubuntu-latest
steps:
- name: Auto-label and triage new issues
uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9
with:
github-token: ${{ secrets.GITHUB_TOKEN }}
script: |
const issue = context.payload.issue;
const body = issue.body || '';
const title = issue.title || '';
const bodyLower = body.toLowerCase();
const titleLower = title.toLowerCase();
const labelsToAdd = [];
// Always remove legacy/default triage label on issue open.
try {
await github.rest.issues.removeLabel({
owner: context.repo.owner,
repo: context.repo.repo,
issue_number: issue.number,
name: 'needs triage',
});
core.info('Removed label: needs triage');
} catch (error) {
if (error.status !== 404) {
throw error;
}
core.info('Label not present: needs triage');
}
// --- Map bug_category / feature_category dropdown to area labels ---
const categoryAreaMap = {
'setup/configuration': 'area: setup',
'config flow': 'area: setup',
'system monitoring': 'area: system',
'cpu, ram': 'area: system',
'storage monitoring': 'area: storage',
'array': 'area: storage',
'docker management': 'area: docker',
'container': 'area: docker',
'vm management': 'area: vm',
'virtual machine': 'area: vm',
'ups monitoring': 'area: ups',
'battery status': 'area: ups',
'notification': 'area: notifications',
'unraid alerts': 'area: notifications',
'network monitoring': 'area: infra',
'shares': 'area: storage',
'parity': 'area: storage',
'configuration': 'area: setup',
'services': 'area: infra',
'service management': 'area: infra',
'registration': 'area: infra',
'plugins': 'area: infra',
'cloud': 'area: infra',
};
for (const [keyword, label] of Object.entries(categoryAreaMap)) {
if (bodyLower.includes(keyword)) {
labelsToAdd.push(label);
break; // Only apply first matching area label
}
}
// --- Detect entity/performance/websocket from title or body ---
const keywordLabelMap = {
'websocket': 'area: websocket',
'real-time': 'area: websocket',
'container stats': 'area: docker',
'parity check': 'area: storage',
'smart': 'area: storage',
'array': 'area: storage',
};
for (const [keyword, label] of Object.entries(keywordLabelMap)) {
if ((titleLower.includes(keyword) || bodyLower.includes(keyword)) && !labelsToAdd.includes(label)) {
labelsToAdd.push(label);
}
}
// --- Apply labels ---
if (labelsToAdd.length > 0) {
// De-duplicate
const uniqueLabels = [...new Set(labelsToAdd)];
await github.rest.issues.addLabels({
owner: context.repo.owner,
repo: context.repo.repo,
issue_number: issue.number,
labels: uniqueLabels,
});
core.info(`Applied labels: ${uniqueLabels.join(', ')}`);
}
The same workflow, on Latchkey
Removes redundant runs and caps runaway jobs. Added and changed lines are highlighted.
name: Issue Triage on: issues: types: [opened] permissions: issues: write jobs: triage: timeout-minutes: 30 runs-on: latchkey-small steps: - name: Auto-label and triage new issues uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9 with: github-token: ${{ secrets.GITHUB_TOKEN }} script: | const issue = context.payload.issue; const body = issue.body || ''; const title = issue.title || ''; const bodyLower = body.toLowerCase(); const titleLower = title.toLowerCase(); const labelsToAdd = []; // Always remove legacy/default triage label on issue open. try { await github.rest.issues.removeLabel({ owner: context.repo.owner, repo: context.repo.repo, issue_number: issue.number, name: 'needs triage', }); core.info('Removed label: needs triage'); } catch (error) { if (error.status !== 404) { throw error; } core.info('Label not present: needs triage'); } // --- Map bug_category / feature_category dropdown to area labels --- const categoryAreaMap = { 'setup/configuration': 'area: setup', 'config flow': 'area: setup', 'system monitoring': 'area: system', 'cpu, ram': 'area: system', 'storage monitoring': 'area: storage', 'array': 'area: storage', 'docker management': 'area: docker', 'container': 'area: docker', 'vm management': 'area: vm', 'virtual machine': 'area: vm', 'ups monitoring': 'area: ups', 'battery status': 'area: ups', 'notification': 'area: notifications', 'unraid alerts': 'area: notifications', 'network monitoring': 'area: infra', 'shares': 'area: storage', 'parity': 'area: storage', 'configuration': 'area: setup', 'services': 'area: infra', 'service management': 'area: infra', 'registration': 'area: infra', 'plugins': 'area: infra', 'cloud': 'area: infra', }; for (const [keyword, label] of Object.entries(categoryAreaMap)) { if (bodyLower.includes(keyword)) { labelsToAdd.push(label); break; // Only apply first matching area label } } // --- Detect entity/performance/websocket from title or body --- const keywordLabelMap = { 'websocket': 'area: websocket', 'real-time': 'area: websocket', 'container stats': 'area: docker', 'parity check': 'area: storage', 'smart': 'area: storage', 'array': 'area: storage', }; for (const [keyword, label] of Object.entries(keywordLabelMap)) { if ((titleLower.includes(keyword) || bodyLower.includes(keyword)) && !labelsToAdd.includes(label)) { labelsToAdd.push(label); } } // --- Apply labels --- if (labelsToAdd.length > 0) { // De-duplicate const uniqueLabels = [...new Set(labelsToAdd)]; await github.rest.issues.addLabels({ owner: context.repo.owner, repo: context.repo.repo, issue_number: issue.number, labels: uniqueLabels, }); core.info(`Applied labels: ${uniqueLabels.join(', ')}`); }
What changed
- Run on Latchkey managed runners with one line (
runs-on), which apply the fixes below automatically and self-heal transient failures. This example useslatchkey-small; pick the runner size that fits the job. - Add a job timeout so a hung step cannot burn hours of runner time.
This workflow runs 1 job per trigger. On Latchkey the same minutes cost up to 58% less than GitHub-hosted, with zero queue time.