0 none, 1 urgent, 2 high, 3 medium, 4 low. We map our four severity bands straight onto them so the classification output drops into the Linear node with no translation step.
| Severity | Meaning | Linear priority | Slack target |
|---|---|---|---|
| S0 | Production down, data loss, payment broken | 1 (Urgent) | @oncall + DM the lead |
| S1 | Core flow broken, no workaround | 2 (High) | Owning squad channel |
| S2 | Bug with a workaround, cosmetic-but-visible | 3 (Medium) | Owning squad channel |
| S3 | Minor cosmetic, edge case, low impact | 4 (Low) | Backlog channel, no ping |
## The cost math vs triaging by hand
The workflow doesn't decide what to build. It does the sorting so your lead engineer spends the morning fixing, not filing. The math turns positive at roughly 20 reports a day.
{
"node": "Webhook",
"parameters": {
"httpMethod": "POST",
"path": "bug-intake",
"responseMode": "onReceived"
}
}{
"node": "Set",
"parameters": {
"values": {
"string": [
{ "name": "source", "value": "={{$json.__source}}" },
{ "name": "reporter", "value": "={{$json.email || $json.user}}" },
{ "name": "subject", "value": "={{$json.subject || $json.text.slice(0,80)}}" },
{ "name": "body", "value": "={{$json.body || $json.text}}" }
]
}
}
}subject + body, check Postgres, and drop anything you've already seen in the last 24 hours.
SELECT 1 FROM bug_seen
WHERE content_hash = $1 AND seen_at > now() - interval '24 hours'
LIMIT 1;{{ $json.severity }} without a separate JSON.parse step.
You are a bug-triage assistant for a B2B SaaS team.
Return ONLY valid JSON in this exact shape, no prose:
{
"severity": "S0|S1|S2|S3",
"product_area": "billing|auth|dashboard|api|mobile|other",
"title": "<6-10 word issue title, imperative>",
"has_repro_steps": true|false,
"duplicate_likelihood": 0.0-1.0,
"confidence": 0.0-1.0,
"reasoning": ""
}
Severity rules:
- S0: production down, data loss, payments failing, security issue
- S1: a core user flow is broken with no workaround
- S2: a bug that has a workaround, or a visible cosmetic defect
- S3: minor cosmetic, rare edge case, low user impact
If repro steps are missing, set has_repro_steps=false (we will ask the reporter).
If you are below 0.7 confident on severity, lower confidence and we route to a human.
Never invent a severity to look decisive. Under-call rather than over-call.
Report:
Subject: {{$json.subject}}
Body: {{$json.body}}
Source: {{$json.source}} {
"node": "Anthropic",
"parameters": {
"model": "claude-haiku-4-5",
"maxTokens": 400,
"temperature": 0.1,
"system": "Return only valid JSON. No markdown fences, no prose.",
"responseFormat": "json"
}
}{
"node": "Linear",
"parameters": {
"resource": "issue",
"operation": "create",
"teamId": "={{$json.team_id}}",
"title": "={{$json.title}}",
"additionalFields": {
"priorityId": "={{ $json.severity === 'S0' ? 1 : ($json.severity === 'S1' ? 2 : ($json.severity === 'S2' ? 3 : 4)) }}",
"labelIds": "={{ [$json.product_area_label_id, $json.ai_triaged_label_id] }}",
"description": "Reported via {{$json.source}} by {{$json.reporter}}.\n\n{{$json.body}}\n\n_AI severity {{$json.severity}} (confidence {{$json.confidence}}). {{$json.reasoning}}_"
}
}
}{
"node": "Slack",
"parameters": {
"operation": "post",
"channel": "={{$json.squad_channel}}",
"text": "🐞 {{$json.title}} — sev {{$json.severity}} ({{$json.product_area}})\nLinear: {{$json.issue_url}}\nConfidence {{$json.confidence}}. React ✅ to confirm or 🔁 to reclassify."
}
}ai-triaged label and a product-area label, plus a Slack message to the owning squad. S0 issues additionally ping on-call. Every report is logged in Postgres with its hash and the model's verdict.subject + body, check Postgres, and link rather than duplicate. Cross-channel dedupe is the single most valuable node here.
Mistake 3 — trusting the title blindly. Claude writes clean titles, but it occasionally summarises away the one detail that matters (a specific error code). Keep the full original body in the Linear description. The title is for the board; the body is for the fix.
Mistake 4 — sending raw reports with PII to the model. Bug reports paste in emails, session tokens, sometimes a full request payload. Strip obvious PII and secrets before the Claude call. We run a short presidio-analyzer pre-processor and redact to [EMAIL] and [TOKEN] tokens. For data residency, Claude on AWS Bedrock in Mumbai is the fallback.
Mistake 5 — no eval set, so prompt edits regress silently. Hand-label 40 past reports with the correct severity. Re-run them after every prompt change and track accuracy per severity band. The day someone "improves" the prompt and S0 recall drops, you want to know before it ships.
- You take ≥20 bug reports a day across two or more channels
- You use Linear (or can map the priority integers to Jira/GitHub Issues)
- You self-host n8n on Hetzner, DigitalOcean, or your own box
- You have a Postgres instance for cross-channel dedupe and an audit log
- You hand-label ≥40 past reports as an eval set before going live
- You strip PII and secrets before sending report text to Claude
- You keep a human gate on S0 paging for the first month
- You post to Slack with a confirm/reclassify prompt, not silent auto-filing
- You re-run the eval set after every prompt change
has_repro_steps to false. We branch on that: the workflow auto-replies to the reporter asking for steps, expected behaviour, and environment, then holds the ticket in a "needs info" state. About a third of form reports lack usable repro steps, so this branch matters more than people expect.
### Won't the Claude cost spike if we get a flood of reports?
Each classification is a short call — roughly ₹0.14 at 40 reports a day, about ₹1,680 a month. A 10x flood would push Claude cost to roughly ₹16,800, still below one engineer-hour a day. Add a daily spend cap in your monitoring and a circuit-breaker node that pauses classification if the day's call count crosses a threshold.
### Can the workflow auto-assign issues to specific engineers?
It can route to the owning squad's channel and set the team on the Linear issue. We stop short of auto-assigning to a named person — squad leads still pick up work based on current load, which the workflow can't see. Auto-assign only after you've watched the routing be correct for a month.
### How long does this take to build from scratch?
For someone comfortable with n8n: one focused day for the happy path (intake, normalise, classify, create issue, Slack post), and a second day for dedupe, the eval set, PII redaction, and monitoring. Budget a third day for retry logic and a dead-letter queue before you trust it in production.
Want Bug-Triage Automation Wired Into Your Stack?
We build n8n + Claude triage workflows for product teams on Linear, Jira, or GitHub Issues. Setup in 5 working days. Typical project ₹60,000 fixed scope, ₹3,900–₹16,000 monthly run cost. Eval suite, PII redaction, and dashboards included. No slides — just your intake mess and our honest take.
Book a 20-min Call
