I've been publishing n8n workflows for a few weeks now. Every one of them worked on my n8n. That's the problem: my n8n has credentials I created months ago, URLs I pasted in and forgot about, and a version of me who already knows which node needs editing.
So I did the boring thing I should have done before publishing anything. I took 10 of my own workflows, spun up a completely fresh n8n, and set each one up using only the instructions I actually ship with it. No memory, no shortcuts.
Short version: all 10 imported cleanly and all 10 activated. 3 of the 10 still failed to do their job, and the two worst failures showed up as green, successful executions.
The setup
- n8n 1.123.84, installed from npm, running on Node 24, with a brand-new, empty user folder. No credentials, no other workflows, nothing carried over.
- The 10 workflows: a lead qualifier that pings Slack, a support FAQ agent, a cold email personalizer, an invoice extractor, a daily market digest from RSS to Slack, meeting notes to action items, a review-reply agent, a content repurposer, a resume screener, and a weekly metrics narrator that posts to Slack. Two of them (the lead qualifier and the review-reply agent) are the ones in my free repo. The files I tested are byte-for-byte the same as the repo copies.
- The model: a small mock OpenAI-compatible server on localhost that returns valid JSON in the shape each workflow asks for. This post is about whether the workflows survive a fresh install, not whether a model gives good answers, so I took the model out of the equation. Slack and the metrics endpoint were mocked locally too.
All 10 have the same basic shape: a trigger (webhook or schedule) → a Config Code node → a Build Prompt Code node → an HTTP Request to /chat/completions → a Parse Result Code node → respond or post somewhere.
The rule I set before running anything
A workflow passes if, after doing only the documented setup steps, one realistic run finishes and does its job: returns the result to the caller, or posts the message to Slack.
"Documented setup" meant exactly two sources:
- The repo README's quick start: create a Header Auth credential (
Authorization/Bearer YOUR_KEY), select it on the LLM node, and setbaseUrlandmodelin Config. - Whatever the workflow's own Read Me sticky note tells you to do.
If a step wasn't written down, I didn't do it. That's what a new user would do.
Stage 1: is the JSON even valid?
I wrote a small script that parses each file and checks every node's type and typeVersion against the node list that ships with n8n 1.123.84.
| Check | Result |
|---|---|
| Valid JSON | 10 / 10 |
| Every node type exists | 10 / 10 |
Every typeVersion is still supported |
10 / 10 |
| Running the latest version of every node | 0 / 10 |
None of them use the newest node versions. The Webhook nodes are v2 (latest is 2.1), HTTP Request is 4.2 (4.3), Respond to Webhook is 1.1 (1.5), and RSS Feed Read is v1 (1.2). n8n keeps old node versions working, so this isn't a failure, and I was a bit disappointed that my "deprecated node" theory didn't pan out.
The other thing the script flagged: none of the files have a workflow id. That turned out to matter later.
Stage 2: import
I imported each file one at a time with n8n import:workflow.
10 / 10 imported. No errors, no warnings about my files.
Stage 3: activate before setting anything up
Here's where it got interesting. Before creating any credential, I tried activating all 10 workflows exactly as imported. Every LLM node still points at a credential with the id REPLACE_ME, which doesn't exist on this install.
10 / 10 activated. n8n happily turned all of them on.
Then I called each of the 8 webhook workflows once with a realistic payload:
| What the caller saw | What the execution log saw |
|---|---|
| HTTP 200, empty body, in about 2 seconds | ❌ Error at the LLM node: Credential with ID "REPLACE_ME" does not exist for type "httpHeaderAuth".
|
All 8 behaved the same way. If you've got a form, a Zap or a script calling one of these webhooks, it gets a 200 and moves on. Unless you check for an empty body, nothing on the calling side looks wrong.
To be clear, that's a "you didn't set it up yet" situation, so I don't count it as a failure. But it's a good reminder that "active" in n8n means "the trigger is listening", not "this workflow works".
Stage 4: the documented setup
I created one Header Auth credential, selected it on every LLM node, and pointed baseUrl / model in each Config node at the mock server. Then I did what each sticky note said:
- Daily market digest: "Set Header Auth credential on LLM; paste Slack webhook URL." So I pasted a Slack webhook URL (the local mock).
- Weekly metrics narrator: "Point Fetch Metrics at any JSON." So I pointed it at a local metrics JSON.
- Everything else: the sticky only covers the credential and Config, which I'd already done.
Stage 5: run each one once
| # | Workflow | Execution | Did its job? | Result |
|---|---|---|---|---|
| 01 | Lead qualifier → Slack | ✅ success | ❌ no Slack alert | FAIL |
| 02 | Support FAQ agent | ✅ success | ✅ | pass |
| 03 | Cold email personalizer | ✅ success | ✅ | pass |
| 04 | Invoice extractor | ✅ success | ✅ | pass |
| 05 | Daily market digest → Slack | ❌ error at Read RSS | ❌ | FAIL |
| 06 | Meeting notes → actions | ✅ success | ✅ | pass |
| 07 | Review-reply agent | ✅ success | ✅ | pass |
| 08 | Content repurposer | ✅ success | ✅ | pass |
| 09 | Resume screener | ✅ success | ✅ | pass |
| 10 | Weekly metrics → Slack | ✅ success | ❌ no Slack post | FAIL |
7 passed, 3 failed. The pattern was obvious: every workflow that only talks back to its own caller passed. All 3 that reach out to another service failed.
Here's each failure.
Failure 1: the lead qualifier "posted" to Slack and nothing arrived
The webhook returned exactly what it should:
{"score": 82, "tier": "hot", "summary": "Series A SaaS, 40 staff, budget approved, wants a demo this week", "reasons": ["ICP fit", "budget stated", "timeline < 30 days"], "next_step": "Book a demo"}
Score 82 is above the 70 threshold, so the Hot Lead? IF node sent it down the true branch to Post to Slack. The execution went green and the mock Slack got nothing.
The reason: Post to Slack still had my placeholder URL in it:
https://hooks.slack.com/services/REPLACE/WITH/YOUR_WEBHOOK
You'd expect that to fail loudly. It doesn't. Slack answers that URL with a 302 redirect to its developer docs page. The HTTP Request node follows the redirect by default, gets a 200 with a page of HTML, and records it as a success. The node output is literally the Slack docs homepage:
{"data": "<!doctype html><html lang=en dir=ltr class=\"docs-wrapper plugin-docs ...\"><title data-rh=true>Slack developer docs | Slack Developer Docs</title>..."}
And the sticky note never says to change the Slack URL. It tells you about the credential and Config, then stops. So a new user does everything the instructions say, sees green executions, and never gets a single hot-lead alert. That's the worst kind of failure, because there's nothing telling you to go and look.
Failure 2: the weekly metrics narrator, same bug
Same placeholder, same redirect, same green execution, no Slack message. Its sticky note only mentions pointing Fetch Metrics at your JSON. It doesn't mention the Slack URL, and it doesn't mention the LLM credential either (I only knew to set that from the repo README).
I'd written the market digest's note properly ("paste Slack webhook URL") and then forgot to write the same line on two other workflows. I'm fairly sure that's how most setup bugs happen: the instructions get written for the workflow you built first.
Failure 3: the market digest couldn't read its feed
This one failed loudly, at the very first node after the trigger:
Read RSS: socket hang up
The default feed is https://news.ycombinator.com/rss. From my test server, that host never finishes a TLS handshake. curl fails the same way, while hnrss.org, TechCrunch's feed and the dev.to feed all return 200 from the same machine. So this one is partly about my environment rather than the JSON itself.
I'm still counting it, because it's exactly what a fresh install in a new environment looks like. A cloud VM, a corporate network or a home server can each have their own idea of which hosts they can reach. The workflow depends on one hardcoded feed with no fallback, so when that one host is unreachable, the whole digest just doesn't happen. (At least this one shows up as a red execution.)
Checking that those really were the only problems
To make sure I hadn't missed anything else, I changed exactly one thing in each failed workflow and ran it again:
| Workflow | One change | Result |
|---|---|---|
| 01 Lead qualifier | real Slack URL in Post to Slack | ✅ success, Slack alert arrived |
| 05 Market digest | feed → https://hnrss.org/frontpage
|
✅ success, digest posted |
| 10 Metrics narrator | real Slack URL in Post to Slack | ✅ success, summary posted |
So none of the three were broken JSON. They were broken setup instructions plus a single point of failure.
Bonus failure: importing the same file twice
The missing workflow id from stage 1 shows up here. I imported the review-reply workflow a second time, like you might after tweaking the file:
- n8n created a second copy instead of updating the first one (no id, so nothing to match on).
- Activating the second copy failed with: "The URL path that the "Webhook" node uses is already taken. Please change it to something else."
That's a reasonable error, and at least it's loud. But if you deactivate the old copy and activate the new one, you can end up with two near-identical workflows and no clear sign of which one is serving your webhook.
The fix I'm using now: make Config refuse to run until setup is done
The real problem behind failures 1 and 2 is that the values a user has to edit are scattered across different nodes, and the placeholders don't fail. So I moved every "you must change this" value into the Config node and made it throw if any of them still look like a placeholder:
// Edit these values. Any OpenAI-compatible endpoint works (OpenAI, Groq, OpenRouter, Together, local Ollama /v1).
const cfg = {
baseUrl: 'https://api.openai.com/v1',
model: 'gpt-4o-mini',
slackWebhook: 'https://hooks.slack.com/services/REPLACE/WITH/YOUR_WEBHOOK',
};
// Fail loudly if setup isn't finished, instead of "succeeding" against a placeholder.
const unset = Object.entries(cfg)
.filter(([, v]) => !v || /REPLACE|YOUR_|example\.com/i.test(v))
.map(([k]) => k);
if (unset.length) throw new Error(`Setup not finished: set ${unset.join(', ')} in the Config node`);
return [{ json: { ...$input.first().json, ...cfg }, pairedItem: 0 }];
Then Post to Slack reads its URL from Config instead of hardcoding it:
{{ $node['Config'].json.slackWebhook }}
A small gotcha I hit here: on this n8n version, {{ $('Config').first().json.slackWebhook }} in the HTTP Request URL field came back as undefined ("URL parameter must be a string, got undefined"), even though Config's output clearly had the value. $node['Config'].json.slackWebhook worked. I didn't dig into why. If you hit the same error, try the other syntax.
I tested the fixed lead qualifier on the same fresh install, twice:
| Slack URL in Config | Execution | Slack |
|---|---|---|
| placeholder left in | ❌ error at Config: Setup not finished: set slackWebhook in the Config node
|
nothing sent |
| real URL | ✅ success | alert arrived |
Now the forgotten step turns into a red execution with an error message that tells you exactly what to fix. It also means an Error Trigger workflow (the setup from my earlier post on silent failures) will catch it.
The caller still gets an HTTP 200 with an empty body when the workflow errors before Respond to Webhook, like in stage 3. If your caller matters, treat an empty body as a failure on that side too.
What I'm changing in how I ship workflows
-
Every value a user must change lives in one Config node, and Config throws if any of them still contain
REPLACE,YOUR_orexample.com. - The sticky note lists every required step, generated from that Config list, not written from memory.
-
External calls get checked, not just executed. A 200 from a webhook URL that redirected to a docs page isn't a success. For Slack specifically, a real incoming webhook replies with
ok, so checking for that catches the redirect case. - Default feeds and URLs should be ones that work almost everywhere, or come with a fallback. A digest that depends on one host is a digest that silently doesn't arrive one morning.
-
Ship workflow files with a stable
id, so re-importing updates the workflow instead of creating a copy. - Run this test before publishing. Fresh user folder, import, follow only the written steps, run once. It took me under an hour for 10 workflows, and it found problems I'd shipped weeks ago.
The free workflows
The lead qualifier and the review-reply agent are in the free repo: arkintof/n8n-ai-agent-workflows, under workflows/. If you're using the lead qualifier from there, paste your Slack webhook URL into Post to Slack, because its Read Me doesn't tell you to. Or replace its Config node with the guard above and point Post to Slack at {{ $node['Config'].json.slackWebhook }}.
When was the last time you imported one of your own workflows into a clean n8n and followed only your own instructions? And how do you catch a "success" that didn't actually do anything?
Written with AI assistance; workflow built and tested by me.
Top comments (0)