Automating GitHub Issues: Templates, Labels, and Triage Bots
Standard templates, reusable labels, and a little triage bot turn a noisy public issue queue into an actionable backlog, and nothing real slips through.
Netbay Developer Relations
Netbay Engineering
On this page
Issues are the raw input of almost every repository, yet most projects send them through an unmarked door. A blank textarea collects typos, half-empty bug reports, and feature requests that take three follow-up questions to become actionable. That is precisely what templates, labels, and a lightweight triage bot fix. Templates make the reporter do the minimum useful work at submit time, labels turn the queue into a queryable dataset, and automation takes over the chores you would otherwise do by hand.
Templates and forms
GitHub supports two kinds of issue templates. A classic markdown template is a file of instructions the reporter sees when the field is empty. A form schema, configured in YAML, renders structured fields — checkboxes, dropdowns, and textareas — and marks some of them required. Forms are the better default because they *enforce* structure instead of suggesting it: an empty reproduction box is impossible to submit if you refuse blank rows.
# .github/ISSUE_TEMPLATE/config.yml
blank_issues_enabled: false
# .github/ISSUE_TEMPLATE/bug.yml
name: Bug report
description: Something is broken
title: "[bug] "
labels: [kind/bug, needs-triage]
body:
- type: textarea
id: repro
attributes:
label: Steps to reproduce
placeholder: Exact commands, input, and output
validations:
required: true
- type: textarea
id: expected
attributes:
label: Expected behaviour
validations:
required: true
- type: input
id: version
attributes:
label: Version
placeholder: e.g. 1.4.2
validations:
required: true
- type: dropdown
id: severity
attributes:
label: Impact
options: [blocks us, slows us, cosmetic]Keep one template per issue type and keep them short. Every extra required field is a tax the reporter pays; five questions is a good ceiling. The "labels" key pre-stamps the issue, which is your first automation point — the reporter does the labeling at zero cost to you.
Labels as a vocabulary, not a color picker
Labels are the cheapest database you will ever operate, but only if they form a taxonomy. A pile of hand-decorated colors is noise; a small vocabulary reads like a filter you can teach a new contributor in a minute:
- **kind/** — bug, feature, question, chore.
- **area/** — web, api, cli, docs, infra.
- **priority/** — p0 down to p3; assigned by triage, never by the reporter.
- **status/** — needs-repro, blocked, doing, review.
- **flagged/** — security, privacy, licensing.
The discipline matters more than the exact words: every label is a question you can ask the queue later, like "show me everything touching the api marked blocked." If a label never gets queried, it is decoration — delete it.
Triaging with a small workflow
A triage loop has three jobs: stamp every new issue, catch the obvious ones instantly, and leave a predictable trail. A workflow on the issues event does the first two. Notice this is repo operations, not CI — it lives in .github/workflows but it is bookkeeping, and it runs on the issue event rather than a push.
name: triage
on:
issues:
types: [opened]
jobs:
triage:
runs-on: ubuntu-latest
permissions:
issues: write
steps:
- uses: actions/ecosystem/action-add-labels@v1
with:
labels: needs-triage
- name: flag sev1 mentions instantly
if: contains(github.event.issue.body, 'SEV1')
uses: actions/ecosystem/action-add-labels@v1
with:
labels: priority/p0The workflow does not close anything and does not assign heroes — it only normalizes input so a human triage pass is fast. Deciding a p0 actually exists is still a human call; the bot just makes sure nothing sits in the default pile unremarked.
Practical: batch maintenance with gh
Once labels exist, the GitHub CLI makes bulk bookkeeping safe and scriptable. A triage sweep stops being hand-clicking and becomes a query:
# what is still untangled, sorted by last touch
gh issue list --label needs-triage --state open --search 'updated:<2026-03-01' --json number,title,updatedAt
# every security-flagged issue, as a one-shot CSV-ish line
gh issue list --label flagged/security --state open --json number,title,assignees --jq '.[] | "(.number) (.title) (.assignees | length)"'Run the first command weekly and the untriaged pile becomes a number you can trend instead of a doomscroll.
Where the automation ends
Triage bots sort; they do not judge. The label p0 says "look now," not "this is panicking." Keep a human pass with a written SLA — untriaged issues reviewed within one working day, p0 within the hour — and let the bot enforce the bookkeeping around that cadence rather than replacing it.
The useful metric to watch is not total issues but time-to-triage: how long between an issue opening and its first label or assignment. When that number falls to minutes for everything the form catches and a working day for the rest, the loop is closed. These are the two signals that tell you the templates and the workflow are actually paying for themselves, rather than just moving the noise around.
Takeaway: forms raise the floor on report quality, a small label taxonomy makes the queue queryable, and a tiny workflow plus gh keep the loop closed without a human babysitting every ticket. If you are building this sort of tooling around your tracker, the bot and the repo mirror can run on a Lucknow DC01 VPS from Netbay — ready in under 60 seconds at netbayhosts.in.
Keep reading
Follow along on a real VPS
Deploy Linux in under 60 seconds
These guides are written against Ubuntu, Debian, and RHEL-family images — the same ones on NetBay.
Deploy an instance