A 22-person Pune SaaS team got bug reports through four channels: a website form, a support inbox, a #bugs Slack channel, and the founder's DMs. Triaging them ate the lead engineer's first 90 minutes every morning. We wired n8n to catch all four, classify severity with Claude Haiku 4.5, open a Linear ticket, and ping the right squad in Slack. Median time from "user hits a bug" to "ticket assigned with a severity label" dropped from 6 hours to 4 minutes. This post is the exact node graph, the classification prompt, and the five things that broke in week one.
What this bug-triage workflow does, in 55 words
n8n watches four intake points — a webhook form, an email inbox, a Slack channel, and a forwarding address. For each new report it pulls the text, asks Claude for a strict-JSON verdict (severity, area, duplicate-likelihood, a one-line title), creates a Linear issue with the matching priority and labels, then posts a threaded Slack message to the owning squad. A human still confirms.
Why this matters now (June 2025)
n8n's built-in Linear node covers create, update, delete, comment, and link on issues, and authenticates with a personal API key from Settings → API. That means you no longer hand-write GraphQL mutations for the common path. Pair it with Claude Haiku 4.5 for short classifications and the "is auto-triage worth the build?" question flips to yes for any team taking more than ~20 reports a day. We shipped this for a client in March 2026 and have run it on our own intake since.
The actual workflow — node by node
How severity maps to Linear priority
Linear priorities are integers: 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.
The DIY walkthrough — every node, every config
We run this on a self-hosted n8n v1.74.1 on a Hetzner CX22 (tested May 2026), a small Postgres for dedupe and an audit log, and a Linear personal API key. As Hrishikesh, our CTO, puts it: the trick is to make the workflow boring and the prompt strict.
Step 1 — the four intake triggers
The Webhook node gives you a URL to point your website's bug form at. The Slack Trigger fires on new messages in #bugs. Two IMAP Email nodes poll the support inbox and a forwarding address every two minutes.
{
"node": "Webhook",
"parameters": {
"httpMethod": "POST",
"path": "bug-intake",
"responseMode": "onReceived"
}
}Step 2 — normalise every source to one shape
A Set node maps each source into a single object so the rest of the graph doesn't care where the report came from. Build the hash here too.
{
"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}}" }
]
}
}
}Step 3 — skip duplicates with a Postgres check
Cron-style email polling and a Slack repost will hand you the same bug twice. Hash 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;Step 4 — the Claude classification prompt
This is the prompt that does the work. We send it through the Anthropic node with structured output on, so n8n parses the response straight into {{ $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": "<one sentence>"
}
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}}Step 5 — Anthropic node config
{
"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"
}
}Step 6 — create the Linear issue
The built-in Linear node takes the title, a formatted description, the team ID, the priority integer, and labels. We build the priority with a small inline expression off the severity field.
{
"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}}_"
}
}
}Step 7 — route in Slack with a confirm prompt
For S0, the Slack node pings @oncall and DMs the lead. For S1 and S2, it posts to the owning squad channel with the Linear link and two buttons: Confirm, or Reclassify. For S3 it drops a quiet note in a backlog channel with no ping.
{
"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.Common mistakes — five things that broke in week one
Mistake 1 — auto-creating S0 incidents without a human gate. An early version paged on-call directly from the model. The first false S0 (a user yelling in caps about a cosmetic bug) woke an engineer at 2 am. Now S0 posts to on-call but a human acknowledges before the incident process starts.
Mistake 2 — no dedupe across channels. A user reported the same crash in the Slack channel and the form. Two tickets, two people working it. Hash 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.
Real example — 22-person SaaS, Pune
Our client builds B2B inventory software, about 90 customers, roughly 40 bug reports a day across four channels. Before the workflow: the lead engineer spent 90 minutes each morning sorting, and S0 issues sometimes sat unseen until lunch. After deploying this exact graph in March 2026: median report-to-triaged-ticket time fell to 4 minutes, S0 issues now surface within minutes, and the lead got those 90 minutes back for actual engineering.
The unexpected payoff was visibility. With every bug carrying a product-area label from day one, the founder pulled a simple Linear report and found the billing-export feature drove 34% of S1 bugs while serving only 9% of customers. They froze new work for a sprint and fixed the root cause. S1 volume dropped 22% the next month. The triage workflow paid for itself by surfacing product debt, not just by saving sorting time. We saw the same pattern of structure-first reporting building Radiant Finance's internal ops tooling.
This is the same discipline our AI and automation team applies on every workflow build: a strict prompt, a human gate, and an audit log. For the weekly-cadence version of this idea — a Monday retro instead of real-time triage — see our 2025 post on the n8n Slack + Linear weekly bug triage that replaced the Monday standup. For the closely related support-desk flavour, see n8n support-ticket triage with Freshdesk and Zoho.
- 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
FAQ
Can I use Jira or GitHub Issues instead of Linear?
Yes. Swap the Linear node for the Jira or GitHub node and remap the four severity bands to that tracker's priority field. Jira uses named priorities (Highest to Lowest); GitHub uses labels only, so you'd encode severity as a label. The Claude classification step is tracker-agnostic.
How accurate is the severity classification, really?
In our eval set of 40 hand-labeled reports, Claude Haiku 4.5 matched the human severity 81% of the time, with most misses being one band off (an S2 called S1). It under-calls more than it over-calls, which is the safer direction. Always keep the human confirm step rather than treating 81% as good enough to auto-close.
What happens to reports with no reproduction steps?
The prompt sets 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. A 10x flood would still keep Claude cost 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. Eval suite, PII redaction, and dashboards included. No slides — just your intake mess and our honest take.
Book a 20-min Call
