Build an AI bug-triage system
Categorise and prioritise Northwind's reported issues automatically — type, severity, owner.
Published Jan 22, 2026
Bug reports land in Northwind’s tracker faster than anyone can sort them. Each one needs the same three judgements before work can start — is it actually a bug, how urgent is it, and whose desk does it belong on — and until someone makes those calls the report just sits there. The triage is not hard, it is just relentless, and that is exactly the kind of work to hand off.
This script reads each untriaged row from a bug Sheet and asks Claude to make those three calls in one pass: a type, a severity, and a suggested owner. It writes them straight back into the Sheet, so the tracker stays sorted without anyone having to babysit the queue.
What you’ll need
- A Google Sheet of bug reports with a header row, a
descriptioncolumn, and emptytype,severity, andsuggestedOwnercolumns for the output. - An Anthropic API key saved as
ANTHROPIC_API_KEYin Script Properties — see Store API keys and secrets securely.
The script
// The Sheet that holds incoming bug reports.
const BUGS_SHEET_ID = '1abcBugsId';
/**
* Triages every bug row that has a description but no priority yet,
* filling in type, severity, and suggested owner.
*/
function triageBugs() {
// 1. Read the bug Sheet and map header names to column indexes.
const sheet = SpreadsheetApp.openById(BUGS_SHEET_ID).getSheets()[0];
const [h, ...rows] = sheet.getDataRange().getValues();
if (!rows.length) {
Logger.log('No bug reports — nothing to triage.');
return;
}
const col = Object.fromEntries(h.map((k, i) => [k, i]));
// 2. Walk the rows; triage each one that needs it.
let triaged = 0;
rows.forEach((r, i) => {
// Skip rows already triaged, or with no description to read.
if (r[col.priority] || !r[col.description]) return;
// 3. Ask Claude for a strict JSON verdict — fixed keys, fixed
// allowed values, so the result is always parseable.
const prompt =
'Triage this Northwind bug. Return JSON ' +
'{"type": "bug|feature|question", ' +
'"severity": "critical|high|medium|low", ' +
'"suggestedOwner": "design|dev|ops"}.\n\n' + r[col.description];
const out = JSON.parse(callClaude(prompt));
// 4. Write the three verdicts back into their columns.
sheet.getRange(i + 2, col.type + 1).setValue(out.type);
sheet.getRange(i + 2, col.severity + 1).setValue(out.severity);
sheet.getRange(i + 2, col.suggestedOwner + 1).setValue(out.suggestedOwner);
triaged++;
});
Logger.log('Triaged ' + triaged + ' bugs.');
}
/**
* Minimal Anthropic API call. The key lives in Script Properties — it
* is never pasted into the code.
*/
function callClaude(prompt) {
const key = PropertiesService.getScriptProperties()
.getProperty('ANTHROPIC_API_KEY');
const res = UrlFetchApp.fetch('https://api.anthropic.com/v1/messages', {
method: 'post',
contentType: 'application/json',
headers: { 'x-api-key': key, 'anthropic-version': '2023-06-01' },
payload: JSON.stringify({
model: 'claude-haiku-4-5-20251001',
max_tokens: 150,
messages: [{ role: 'user', content: prompt }],
}),
muteHttpExceptions: true,
});
return JSON.parse(res.getContentText()).content[0].text.trim();
}
How it works
triageBugsopens the bug Sheet, reads every row, and builds acolmap from header names to indexes so the code never depends on column order.- If the Sheet has no data rows it logs that and stops before any API calls.
- For each row it skips anything already triaged or missing a description —
the
r[col.priority]guard means a re-run only handles new reports. - The prompt pins Claude to a strict JSON shape: fixed keys, and a closed set
of allowed values for each one. That is what makes
JSON.parsereliable. - Claude Haiku returns the verdict — Haiku is right for short classification work that does not need deeper reasoning.
- The three fields are written back into their own columns at
i + 2, which accounts for the header row plus the zero-based loop index.
Example run
Suppose the bug Sheet holds two untriaged rows:
| description | type | severity | suggestedOwner |
|---|---|---|---|
| Checkout button does nothing on mobile Safari, can’t pay. | (blank) | (blank) | (blank) |
| Could the dashboard remember my last filter? | (blank) | (blank) | (blank) |
After a run the three columns are filled:
| description | type | severity | suggestedOwner |
|---|---|---|---|
| Checkout button does nothing on mobile Safari, can’t pay. | bug | critical | dev |
| Could the dashboard remember my last filter? | feature | low | design |
A raw queue becomes a sorted one — the blocking payment bug is flagged critical and routed to dev, the nice-to-have lands with design as a low-priority feature.
Run it
This is an on-demand job — run it when reports have piled up:
- In the Apps Script editor, select
triageBugsand click Run. - Approve the authorisation prompt the first time.
- Open the Sheet and review the triage before acting on it.
To triage automatically as reports come in, add a time-driven trigger: open
Triggers (the clock icon), Add Trigger, choose triageBugs, set it
Time-driven on an hourly timer, and save.
Watch out for
JSON.parsewill throw on malformed output. Claude usually returns clean JSON for a tight schema, but if it ever wraps the reply in a code fence the parse fails — strip fences or log the raw reply before tightening the prompt.- The allowed values are fixed in the prompt. If you add an owner or a severity band, update the prompt’s value list too, or Claude will map to the old set.
- A re-run skips triaged rows via the
priorityguard. To re-triage a bug, clear its row first — note the guard checkspriority, so that column must exist even though the script writestype,severity, andsuggestedOwner. - Triage is a suggestion. Treat the suggested owner and severity as a starting point a person can override — Claude reads the description, not the roadmap.
Related
Build an AI keyword-clustering tool
Group Northwind's tracked search terms into topic clusters — for SEO content planning.
Updated Feb 19, 2026
Build an AI customer-churn predictor
Flag at-risk Northwind accounts from behavioural signals — usage, support tickets, billing.
Updated Feb 15, 2026
Build a context-aware AI data validator
Catch values that look wrong in context — '£10' for a Northwind retainer is suspicious.
Updated Feb 7, 2026
Auto-categorize a photo library
Tag Northwind Drive images by visual content — product, team, event, behind-the-scenes.
Updated Feb 3, 2026
Build a document-classification system
Sort Northwind Drive files into types by their content — contracts, briefs, invoices.
Updated Jan 6, 2026