# I wrote a free linter for n8n workflows: 12 checks for the failures that cost money

**URL:** <https://community.n8n.io/t/i-wrote-a-free-linter-for-n8n-workflows-12-checks-for-the-failures-that-cost-money/322370>\
**Category:** Tips & Tricks\
**Created:** [October 18, 2026, 9:20am UTC](https://community.n8n.io/t/i-wrote-a-free-linter-for-n8n-workflows-12-checks-for-the-failures-that-cost-money/322370 "2026-10-18T09:20:36Z")\
**Posts on this page:** 4\
**Page:** 1

<div class="post-metadata">

**Author:** ![ck\_lu](https://dub1.discourse-cdn.com/n8n/user_avatar/community.n8n.io/ck_lu/32/190143_2.png) [@ck\_lu](https://community.n8n.io/u/ck_lu)\
**Post date:** [October 18, 2026, 9:20am UTC](https://community.n8n.io/t/i-wrote-a-free-linter-for-n8n-workflows-12-checks-for-the-failures-that-cost-money/322370/1 "2026-10-18T09:20:36Z")

</div>

I wrote a free linter for n8n workflows: 12 checks for the failures that cost money

Most n8n reviews look at the happy path. The expensive failures are elsewhere: the run that dies quietly, the message that gets marked read and never recorded, the retry that sends twice, the handover note that promises threading the send node cannot do.

I turned the checks I use when auditing workflows into a small script. It reads an exported workflow JSON and reports what it finds. No instance access, no credentials, no network, no dependencies.

**What it checks**

| Severity | Rule | What it catches |
| --- | --- | --- |
| high | `no-error-workflow` | No error workflow set - failures are silent |
| high | `imap-read-before-log` | Mail marked read before it is recorded |
| high | `confidence-defaults-high` | A missing confidence value treated as certain |
| high | `threading-claim-false` | Docs promise `In-Reply-To` that the node cannot set |
| high | `autosend-hardcoded-on` | Auto-send hardcoded, human gate gone |
| medium | `http-no-retry` | A transient 502 kills the whole run |
| medium | `webhook-open` | Webhook accepts unauthenticated requests |
| medium | `no-idempotency` | A retried trigger is processed twice |
| medium | `no-send-log` | Sends mail, records nothing |
| medium | `empty-draft-sendable` | An empty draft can reach the send step |
| low | `trigger-doc-mismatch` | Node name contradicts its schedule |
| low | `http-no-timeout` | Hung HTTP call blocks the queue |

**What it found in my own template**

I ran it against my own published workflow ([Triage inbound email with IMAP, custom AI HTTP APIs and SMTP | n8n workflow template](http://n8n.io/workflows/19734)). Four high findings, including the one I am least proud of: the sticky notes claimed `In-Reply-To` / `References` were set, and the send node had an empty options object. n8n’s Send Email node does not support those headers at all - every reply opens a new conversation. My documentation was describing something impossible, which is worse than simply not threading.

That is the point of the tool: it found the lie in my own handover note before a customer did.

**Usage**

```auto
python n8nwf_lint.py workflow.json
python n8nwf_lint.py workflow.json --json # for CI
python n8nwf_lint.py workflow.json --min-severity high

```

Exit code is 1 when findings meet the threshold, so it works as a CI gate unchanged.

**Repo:** [GitHub - Miss567/n8nwf-lint: Static reliability checks for exported n8n workflows - silent failures, missing retries, false threading claims. No dependencies, MIT. · GitHub](https://github.com/Miss567/n8nwf-lint) - MIT, Python 3.8+, no dependencies.

Two honest notes. It is a static heuristic check on the graph, not a behavioural test - it cannot see runtime data, so it will miss things and may flag things you consider fine. If a rule is wrong, tell me and I will fix it; that is more valuable to me than a star.

And yes, I do this for a living: a fuller audit is USD 45, and fixes are quoted before starting. The tool being free is not a trick - if the report is enough for you to fix it yourself, that is a good outcome.

Sample output: the screenshot below is the real report on my template.

 ![n8nwf-lint sample report](https://europe1.discourse-cdn.com/n8n/original/3X/1/3/13620f56ff14409532eb69a7fc9ceade5f104bb8.png)

**If you want the client-facing part:** the free script prints findings; the paid kit turns the same checks into a branded HTML/PDF audit report you can hand to a client (one command, your name and the client’s name on it): [https://payhip.com/b/ZHoiv](https://payhip.com/b/ZHoiv) - USD 19.

---

<div class="post-metadata">

**Author:** ![Shital\_Katre](https://dub1.discourse-cdn.com/n8n/user_avatar/community.n8n.io/shital_katre/32/190617_2.png) [@Shital\_Katre](https://community.n8n.io/u/Shital_Katre)\
**Post date:** [October 18, 2026, 2:31pm UTC](https://community.n8n.io/t/i-wrote-a-free-linter-for-n8n-workflows-12-checks-for-the-failures-that-cost-money/322370/2 "2026-10-18T14:31:34Z")

</div>

This is a useful approach to n8n reliability. I especially like the checks for idempotency, missing timeouts, unauthenticated webhooks, and send logging. Static linting is a great first gate, though I’d pair it with runtime tests for retries, duplicate events, and failure recovery before calling a workflow production-ready. Thanks for sharing this!

---

<div class="post-metadata">

**Author:** ![ck\_lu](https://dub1.discourse-cdn.com/n8n/user_avatar/community.n8n.io/ck_lu/32/190143_2.png) [@ck\_lu](https://community.n8n.io/u/ck_lu)\
**Post date:** [October 22, 2026, 12:50pm UTC](https://community.n8n.io/t/i-wrote-a-free-linter-for-n8n-workflows-12-checks-for-the-failures-that-cost-money/322370/3 "2026-10-22T12:50:49Z")

</div>

Thanks — and your list is the right gap. Static checks can see the graph; they cannot see what happens when the 429 arrives at 14:03 on a Friday.

On the runtime side, the three I would test first are the same three that keep showing up in real incidents:

1. **Retry idempotency.** Force the HTTP step to fail after the write, let the retry run, and check whether you now have two rows. That is the check nobody writes because it needs a deliberate half-failure.
2. **Duplicate trigger delivery.** Deliver the same webhook twice with the same message id and see whether anything downstream acts twice. Most workflows pass this by accident because the provider de-duplicates, not because the workflow does.
3. **Recovery after a dead step.** Kill the workflow mid-run (disable a node), let a few messages queue, re-enable it, and see whether the missed ones come back. This is where “mark as read on fetch” quietly loses mail.

If you want a starting point beyond words: the static ones are a free Python script (repo in the post), and I packaged the client-facing half — the HTML/PDF report generator, plus two smaller tools for version diffing and turning an export into a diagram — as a paid kit, because agencies kept asking for the deliverable rather than the findings. Link is in the repo README if it is useful; the free checker stays free either way.

What would you add to the runtime list? I would rather the checklist be wrong here than in a client handover.

---

<div class="post-metadata">

**Author:** ![ck\_lu](https://dub1.discourse-cdn.com/n8n/user_avatar/community.n8n.io/ck_lu/32/190143_2.png) [@ck\_lu](https://community.n8n.io/u/ck_lu)\
**Post date:** [October 22, 2026, 1:24pm UTC](https://community.n8n.io/t/i-wrote-a-free-linter-for-n8n-workflows-12-checks-for-the-failures-that-cost-money/322370/4 "2026-10-22T13:24:55Z")

</div>

Small update, since a few people asked what the paid side actually is. All three are live now, one-time prices, no subscription:

- **Workflow reliability audit kit, USD 19** - the same 12 checks, plus a client-ready HTML/PDF report with your name and the client’s name on it: [https://payhip.com/b/ZHoiv](https://payhip.com/b/ZHoiv)
- **Workflow diff, USD 12** - compares two exports and tells you what actually changed (nodes, parameters, retry flags, connections), because “what did you change?” deserves a diff rather than a memory: [https://payhip.com/b/6dHMA](https://payhip.com/b/6dHMA)
- **Workflow diagram, USD 12** - turns an export into a Mermaid diagram and a handover page, so the client sees the flow instead of JSON: [https://payhip.com/b/LydlD](https://payhip.com/b/LydlD)

The free checker is unchanged and stays free: [GitHub - Miss567/n8nwf-lint: Static reliability checks for exported n8n workflows - silent failures, missing retries, false threading claims. No dependencies, MIT. · GitHub](https://github.com/Miss567/n8nwf-lint)

If a check in the free tool is wrong for your workflow, tell me here and I will fix the rule - that is worth more to me than a sale.
