diff --git a/.claude-plugin/marketplace.json b/.claude-plugin/marketplace.json
index 3322d9f4..15d67f51 100644
--- a/.claude-plugin/marketplace.json
+++ b/.claude-plugin/marketplace.json
@@ -27,7 +27,7 @@
"./skills/using-n8n-mcp-skills"
],
"description": "Complete bundle: 14 expert skills for building flawless n8n workflows with the n8n-mcp MCP server β and deploying the self-hosted n8n that runs them (expression syntax, MCP tools usage, workflow patterns, validation, node configuration, JavaScript code, Python code, the AI-agent Custom Code Tool, production error handling, binary/file handling, reusable sub-workflows, AI agents, multi-instance targeting, and end-to-end self-hosted deployment), plus an always-on router skill and a hooks enforcement layer that surfaces the right skill at the moment of decision.",
- "version": "1.21.0",
+ "version": "1.22.0",
"author": {
"name": "Romuald CzΕonkowski",
"url": "https://www.aiadvisors.pl/en"
diff --git a/.claude-plugin/plugin.json b/.claude-plugin/plugin.json
index ad62bb52..f88b6401 100644
--- a/.claude-plugin/plugin.json
+++ b/.claude-plugin/plugin.json
@@ -1,6 +1,6 @@
{
"name": "n8n-mcp-skills",
- "version": "1.21.0",
+ "version": "1.22.0",
"description": "Expert skills for building n8n workflows with n8n-mcp, with a hooks enforcement layer",
"author": {
"name": "Romuald CzΕonkowski",
diff --git a/build.sh b/build.sh
index 96116e6a..fc6f1c69 100755
--- a/build.sh
+++ b/build.sh
@@ -5,7 +5,7 @@
set -e
DIST_DIR="dist"
-VERSION="1.21.0"
+VERSION="1.22.0"
echo "π¨ Building n8n-skills distribution packages..."
diff --git a/dist/README.md b/dist/README.md
index 0643e1c9..622b7969 100644
--- a/dist/README.md
+++ b/dist/README.md
@@ -8,21 +8,21 @@ This folder contains distribution packages for different Claude platforms.
Upload each skill separately via Settings > Capabilities > Skills (bottom of page):
-- `n8n-expression-syntax-v1.21.0.zip` - n8n expression syntax and common patterns
-- `n8n-mcp-tools-expert-v1.21.0.zip` - Expert guide for using n8n-mcp tools (recommended to install first)
-- `n8n-workflow-patterns-v1.21.0.zip` - Proven workflow architectural patterns
-- `n8n-validation-expert-v1.21.0.zip` - Validation error interpretation and fixing
-- `n8n-node-configuration-v1.21.0.zip` - Operation-aware node configuration
-- `n8n-code-javascript-v1.21.0.zip` - JavaScript in n8n Code nodes
-- `n8n-code-python-v1.21.0.zip` - Python in n8n Code nodes
-- `n8n-code-tool-v1.21.0.zip` - Code for the AI-agent Custom Code Tool
-- `n8n-error-handling-v1.21.0.zip` - Production error handling (per-node error outputs, retries, Error Trigger)
-- `n8n-binary-and-data-v1.21.0.zip` - Binary and file data handling
-- `n8n-subworkflows-v1.21.0.zip` - Reusable, composable sub-workflows
-- `n8n-agents-v1.21.0.zip` - AI agent design (Agent vs chain vs classifier, tools, memory)
-- `n8n-multi-instance-v1.21.0.zip` - Targeting the right n8n instance on multi-instance accounts (switch/verify, credential safety)
-- `n8n-self-hosting-v1.21.0.zip` - Deploy self-hosted n8n to a VM (Docker Compose + Caddy auto-TLS, single or queue mode, Day-2)
-- `using-n8n-mcp-skills-v1.21.0.zip` - Always-on router skill (best in the Claude Code bundle, where the SessionStart hook loads it automatically)
+- `n8n-expression-syntax-v1.21.1.zip` - n8n expression syntax and common patterns
+- `n8n-mcp-tools-expert-v1.21.1.zip` - Expert guide for using n8n-mcp tools (recommended to install first)
+- `n8n-workflow-patterns-v1.21.1.zip` - Proven workflow architectural patterns
+- `n8n-validation-expert-v1.21.1.zip` - Validation error interpretation and fixing
+- `n8n-node-configuration-v1.21.1.zip` - Operation-aware node configuration
+- `n8n-code-javascript-v1.21.1.zip` - JavaScript in n8n Code nodes
+- `n8n-code-python-v1.21.1.zip` - Python in n8n Code nodes
+- `n8n-code-tool-v1.21.1.zip` - Code for the AI-agent Custom Code Tool
+- `n8n-error-handling-v1.21.1.zip` - Production error handling (per-node error outputs, retries, Error Trigger)
+- `n8n-binary-and-data-v1.21.1.zip` - Binary and file data handling
+- `n8n-subworkflows-v1.21.1.zip` - Reusable, composable sub-workflows
+- `n8n-agents-v1.21.1.zip` - AI agent design (Agent vs chain vs classifier, tools, memory)
+- `n8n-multi-instance-v1.21.1.zip` - Targeting the right n8n instance on multi-instance accounts (switch/verify, credential safety)
+- `n8n-self-hosting-v1.21.1.zip` - Deploy self-hosted n8n to a VM (Docker Compose + Caddy auto-TLS, single or queue mode, Day-2)
+- `using-n8n-mcp-skills-v1.21.1.zip` - Always-on router skill (best in the Claude Code bundle, where the SessionStart hook loads it automatically)
**Installation:**
1. Go to Settings > Capabilities > Skills (bottom of page)
@@ -32,7 +32,7 @@ Upload each skill separately via Settings > Capabilities > Skills (bottom of pag
### Complete Bundle (Claude Code only)
-- **`n8n-mcp-skills-v1.21.0.zip`** - All 14 skills + the always-on router and the hooks enforcement layer, in one package
+- **`n8n-mcp-skills-v1.21.1.zip`** - All 14 skills + the always-on router and the hooks enforcement layer, in one package
> **This bundle is NOT compatible with Claude.ai or Claude Desktop.** It uses a nested `skills/` directory structure required by Claude Code plugins, and ships hooks that only run under the Claude Code / Codex plugin install. For Claude.ai/Desktop, use the individual skill zips above.
@@ -42,7 +42,7 @@ Upload each skill separately via Settings > Capabilities > Skills (bottom of pag
/plugin install czlonkowski/n8n-skills
# Or install from local file
-/plugin install /path/to/n8n-mcp-skills-v1.21.0.zip
+/plugin install /path/to/n8n-mcp-skills-v1.21.1.zip
```
## Which Package Should I Use?
@@ -59,22 +59,22 @@ Upload each skill separately via Settings > Capabilities > Skills (bottom of pag
```
dist/
-βββ n8n-agents-v1.21.0.zip
-βββ n8n-binary-and-data-v1.21.0.zip
-βββ n8n-code-javascript-v1.21.0.zip
-βββ n8n-code-python-v1.21.0.zip
-βββ n8n-code-tool-v1.21.0.zip
-βββ n8n-error-handling-v1.21.0.zip
-βββ n8n-expression-syntax-v1.21.0.zip
-βββ n8n-mcp-skills-v1.21.0.zip Claude Code only (bundle)
-βββ n8n-mcp-tools-expert-v1.21.0.zip
-βββ n8n-multi-instance-v1.21.0.zip
-βββ n8n-node-configuration-v1.21.0.zip
-βββ n8n-self-hosting-v1.21.0.zip
-βββ n8n-subworkflows-v1.21.0.zip
-βββ n8n-validation-expert-v1.21.0.zip
-βββ n8n-workflow-patterns-v1.21.0.zip
-βββ using-n8n-mcp-skills-v1.21.0.zip
+βββ n8n-agents-v1.21.1.zip
+βββ n8n-binary-and-data-v1.21.1.zip
+βββ n8n-code-javascript-v1.21.1.zip
+βββ n8n-code-python-v1.21.1.zip
+βββ n8n-code-tool-v1.21.1.zip
+βββ n8n-error-handling-v1.21.1.zip
+βββ n8n-expression-syntax-v1.21.1.zip
+βββ n8n-mcp-skills-v1.21.1.zip Claude Code only (bundle)
+βββ n8n-mcp-tools-expert-v1.21.1.zip
+βββ n8n-multi-instance-v1.21.1.zip
+βββ n8n-node-configuration-v1.21.1.zip
+βββ n8n-self-hosting-v1.21.1.zip
+βββ n8n-subworkflows-v1.21.1.zip
+βββ n8n-validation-expert-v1.21.1.zip
+βββ n8n-workflow-patterns-v1.21.1.zip
+βββ using-n8n-mcp-skills-v1.21.1.zip
βββ README.md (this file)
```
diff --git a/dist/n8n-agents-v1.21.0.zip b/dist/n8n-agents-v1.22.0.zip
similarity index 100%
rename from dist/n8n-agents-v1.21.0.zip
rename to dist/n8n-agents-v1.22.0.zip
diff --git a/dist/n8n-binary-and-data-v1.21.0.zip b/dist/n8n-binary-and-data-v1.22.0.zip
similarity index 100%
rename from dist/n8n-binary-and-data-v1.21.0.zip
rename to dist/n8n-binary-and-data-v1.22.0.zip
diff --git a/dist/n8n-code-javascript-v1.21.0.zip b/dist/n8n-code-javascript-v1.22.0.zip
similarity index 54%
rename from dist/n8n-code-javascript-v1.21.0.zip
rename to dist/n8n-code-javascript-v1.22.0.zip
index 9bf54623..85ecd91f 100644
Binary files a/dist/n8n-code-javascript-v1.21.0.zip and b/dist/n8n-code-javascript-v1.22.0.zip differ
diff --git a/dist/n8n-code-python-v1.21.0.zip b/dist/n8n-code-python-v1.22.0.zip
similarity index 100%
rename from dist/n8n-code-python-v1.21.0.zip
rename to dist/n8n-code-python-v1.22.0.zip
diff --git a/dist/n8n-code-tool-v1.21.0.zip b/dist/n8n-code-tool-v1.22.0.zip
similarity index 100%
rename from dist/n8n-code-tool-v1.21.0.zip
rename to dist/n8n-code-tool-v1.22.0.zip
diff --git a/dist/n8n-error-handling-v1.21.0.zip b/dist/n8n-error-handling-v1.22.0.zip
similarity index 100%
rename from dist/n8n-error-handling-v1.21.0.zip
rename to dist/n8n-error-handling-v1.22.0.zip
diff --git a/dist/n8n-expression-syntax-v1.21.0.zip b/dist/n8n-expression-syntax-v1.22.0.zip
similarity index 68%
rename from dist/n8n-expression-syntax-v1.21.0.zip
rename to dist/n8n-expression-syntax-v1.22.0.zip
index bc226578..ec9805de 100644
Binary files a/dist/n8n-expression-syntax-v1.21.0.zip and b/dist/n8n-expression-syntax-v1.22.0.zip differ
diff --git a/dist/n8n-mcp-skills-v1.21.0.zip b/dist/n8n-mcp-skills-v1.22.0.zip
similarity index 79%
rename from dist/n8n-mcp-skills-v1.21.0.zip
rename to dist/n8n-mcp-skills-v1.22.0.zip
index 665efb0b..5c62f5f4 100644
Binary files a/dist/n8n-mcp-skills-v1.21.0.zip and b/dist/n8n-mcp-skills-v1.22.0.zip differ
diff --git a/dist/n8n-mcp-tools-expert-v1.21.0.zip b/dist/n8n-mcp-tools-expert-v1.21.0.zip
deleted file mode 100644
index 5e51a36f..00000000
Binary files a/dist/n8n-mcp-tools-expert-v1.21.0.zip and /dev/null differ
diff --git a/dist/n8n-mcp-tools-expert-v1.22.0.zip b/dist/n8n-mcp-tools-expert-v1.22.0.zip
new file mode 100644
index 00000000..8eb4d329
Binary files /dev/null and b/dist/n8n-mcp-tools-expert-v1.22.0.zip differ
diff --git a/dist/n8n-multi-instance-v1.21.0.zip b/dist/n8n-multi-instance-v1.22.0.zip
similarity index 100%
rename from dist/n8n-multi-instance-v1.21.0.zip
rename to dist/n8n-multi-instance-v1.22.0.zip
diff --git a/dist/n8n-node-configuration-v1.21.0.zip b/dist/n8n-node-configuration-v1.22.0.zip
similarity index 100%
rename from dist/n8n-node-configuration-v1.21.0.zip
rename to dist/n8n-node-configuration-v1.22.0.zip
diff --git a/dist/n8n-self-hosting-v1.21.0.zip b/dist/n8n-self-hosting-v1.22.0.zip
similarity index 100%
rename from dist/n8n-self-hosting-v1.21.0.zip
rename to dist/n8n-self-hosting-v1.22.0.zip
diff --git a/dist/n8n-subworkflows-v1.21.0.zip b/dist/n8n-subworkflows-v1.22.0.zip
similarity index 100%
rename from dist/n8n-subworkflows-v1.21.0.zip
rename to dist/n8n-subworkflows-v1.22.0.zip
diff --git a/dist/n8n-validation-expert-v1.21.0.zip b/dist/n8n-validation-expert-v1.21.0.zip
deleted file mode 100644
index 13711d99..00000000
Binary files a/dist/n8n-validation-expert-v1.21.0.zip and /dev/null differ
diff --git a/dist/n8n-validation-expert-v1.22.0.zip b/dist/n8n-validation-expert-v1.22.0.zip
new file mode 100644
index 00000000..95874c27
Binary files /dev/null and b/dist/n8n-validation-expert-v1.22.0.zip differ
diff --git a/dist/n8n-workflow-patterns-v1.21.0.zip b/dist/n8n-workflow-patterns-v1.22.0.zip
similarity index 100%
rename from dist/n8n-workflow-patterns-v1.21.0.zip
rename to dist/n8n-workflow-patterns-v1.22.0.zip
diff --git a/dist/using-n8n-mcp-skills-v1.21.0.zip b/dist/using-n8n-mcp-skills-v1.21.0.zip
deleted file mode 100644
index 86e7091e..00000000
Binary files a/dist/using-n8n-mcp-skills-v1.21.0.zip and /dev/null differ
diff --git a/dist/using-n8n-mcp-skills-v1.22.0.zip b/dist/using-n8n-mcp-skills-v1.22.0.zip
new file mode 100644
index 00000000..14ba4e96
Binary files /dev/null and b/dist/using-n8n-mcp-skills-v1.22.0.zip differ
diff --git a/skills/n8n-code-javascript/ERROR_PATTERNS.md b/skills/n8n-code-javascript/ERROR_PATTERNS.md
index 65a19252..d263a097 100644
--- a/skills/n8n-code-javascript/ERROR_PATTERNS.md
+++ b/skills/n8n-code-javascript/ERROR_PATTERNS.md
@@ -8,12 +8,12 @@ Complete guide to avoiding the most common Code node errors.
This guide covers the **top 5 error patterns** encountered in n8n Code nodes. Understanding and avoiding these errors will save you significant debugging time.
-**Error Frequency**:
-1. Empty Code / Missing Return - **38% of failures**
-2. Expression Syntax Confusion - **8% of failures**
-3. Incorrect Return Wrapper - **5% of failures**
-4. Unmatched Expression Brackets - **6% of failures**
-5. Missing Null Checks - **Common runtime error**
+**Error frequency (roughly ordered)**:
+1. Empty Code / Missing Return - the dominant error n8n itself rejects
+2. Expression Syntax used as Code - `{{ }}` written where JavaScript belongs
+3. Return Shape - primitive/`null` returns (bare objects auto-wrap)
+4. Broken Strings / Escaping - unbalanced quotes/brackets throw a JS syntax error
+5. Missing Null Checks - common runtime error
---
@@ -115,23 +115,20 @@ if (items.length === 0) {
## Error #2: Expression Syntax Confusion
-**Frequency**: 8% of validation failures
-
-**What Happens**:
-- Syntax error in code execution
-- Error: "Unexpected token" or "Expression syntax is not valid in Code nodes"
-- Template variables not evaluated
+**What Happens** β there are two distinct cases, and only one is a syntax error:
+- **`{{ }}` inside a string literal**: valid JavaScript that runs fine, but you get the *literal text* `{{ ... }}` instead of a value β n8n does not evaluate expressions inside Code-node code. A logic bug, not a validation error. (n8n-mcp β₯ 2.63.0 no longer flags `{{ }}` inside string literals β prompt templates, payload placeholders, and `.replace()` tokens are legitimate.)
+- **`{{ }}` written as bare code** (e.g. `return {{ $json.x }}`): a genuine JavaScript syntax error. The validator reports "Expression syntax {{...}} is not valid in Code nodes" and n8n throws "Unexpected token".
### The Problem
n8n has TWO distinct syntaxes:
1. **Expression syntax** `{{ }}` - Used in OTHER nodes (Set, IF, HTTP Request)
-2. **JavaScript** - Used in CODE nodes (no `{{ }}`)
+2. **JavaScript** - Used in CODE nodes
-Many developers mistakenly use expression syntax inside Code nodes.
+Many developers mistakenly reach for expression syntax inside a Code node when they want a *value*. Putting `{{ }}` in a string does not interpolate it:
```javascript
-// β WRONG: Using n8n expression syntax in Code node
+// β LOGIC BUG: n8n never evaluates {{ }} in Code-node strings
const userName = "{{ $json.name }}";
const userEmail = "{{ $json.body.email }}";
@@ -143,11 +140,12 @@ return [{
}];
// Result: Literal string "{{ $json.name }}", NOT the value!
+// (This runs β it just doesn't do what you meant. Use $json.name directly.)
```
```javascript
-// β WRONG: Trying to evaluate expressions
-const value = "{{ $now.toFormat('yyyy-MM-dd') }}";
+// β SYNTAX ERROR: {{ }} used as code, not inside a string
+const value = {{ $now.toFormat('yyyy-MM-dd') }}; // "Unexpected token"
```
### The Solution
@@ -219,55 +217,48 @@ return [{
---
-## Error #3: Incorrect Return Wrapper Format
+## Error #3: Return Shape
-**Frequency**: 5% of validation failures
+**What actually happens** β n8n is more forgiving than the old advice implied:
+- In *Run Once for All Items* mode, n8n **auto-normalizes** a single bare object, or an array of bare objects, by wrapping each under a `json` property. So these run.
+- What genuinely fails, with "Code doesn't return items properly", is returning a **primitive** (string/number/boolean) or **`null`/`undefined`** β there is nothing to wrap.
-**What Happens**:
-- Error: "Return value must be an array of objects"
-- Error: "Each item must have a json property"
-- Next nodes receive malformed data
+(n8n-mcp β₯ 2.63.0 no longer errors "Return value must be an array of objects" on a bare-object return; the earlier claim contradicted n8n's auto-wrap behavior.)
-### The Problem
+### Prefer the Canonical Form
-Code nodes MUST return:
-- **Array** of objects
-- Each object MUST have a **`json` property**
+The canonical `[{json: {...}}]` is unambiguous and behaves identically in both execution modes, so make it your default even though looser shapes are auto-wrapped:
```javascript
-// β WRONG: Returning object instead of array
+// β οΈ Auto-wrapped β [{json: {result: 'success'}}]. Runs, but prefer the array + json form.
return {
json: {
result: 'success'
}
};
-// Missing array wrapper []
```
```javascript
-// β WRONG: Returning array without json wrapper
+// β οΈ Auto-wrapped β each object gets a json wrapper. Runs, but be explicit.
return [
{id: 1, name: 'Alice'},
{id: 2, name: 'Bob'}
];
-// Missing json property
```
```javascript
-// β WRONG: Returning plain value
-return "processed";
+// β Fine β input items already carry a json property; returning them unchanged is a valid passthrough
+return $input.all();
```
```javascript
-// β WRONG: Returning items without mapping
-return $input.all();
-// Works if items already have json property, but not guaranteed
+// β FAILS: primitive value β nothing to wrap into an item
+return "processed";
```
```javascript
-// β WRONG: Incomplete structure
-return [{data: {result: 'success'}}];
-// Should be {json: {...}}, not {data: {...}}
+// β FAILS: null / undefined β no items to pass on
+return null;
```
### The Solution
@@ -321,11 +312,10 @@ if (shouldProcess) {
### Return Format Checklist
-- [ ] Return value is an **array** `[...]`
-- [ ] Each array element has **`json` property**
+- [ ] Return value is an **array** `[...]` (canonical β preferred)
+- [ ] Each array element has a **`json` property**
- [ ] Structure is `[{json: {...}}]` or `[{json: {...}}, {json: {...}}]`
-- [ ] NOT `{json: {...}}` (missing array wrapper)
-- [ ] NOT `[{...}]` (missing json property)
+- [ ] Not a primitive (string/number/boolean) or `null`/`undefined` β those are the shapes that actually fail
### Common Scenarios
@@ -333,30 +323,30 @@ if (shouldProcess) {
// Scenario 1: Single object from API
const response = $input.first().json;
-// β CORRECT
+// β CANONICAL
return [{json: response}];
-// β WRONG
+// β οΈ Auto-wrapped to the same thing β runs, but prefer the array form
return {json: response};
// Scenario 2: Array of objects
const users = $input.all();
-// β CORRECT
+// β CANONICAL
return users.map(user => ({json: user.json}));
-// β WRONG
-return users; // Risky - depends on existing structure
+// β Also fine β a passthrough of items that already carry json
+return users;
// Scenario 3: Computed result
const total = $input.all().reduce((sum, item) => sum + item.json.amount, 0);
-// β CORRECT
+// β CANONICAL
return [{json: {total}}];
-// β WRONG
+// β οΈ Auto-wrapped β [{json: {total}}] in All Items mode β runs, but be explicit
return {total};
@@ -364,61 +354,55 @@ return {total};
// β CORRECT
return [];
-// β WRONG
+// β FAILS β null has no items to pass on
return null;
```
---
-## Error #4: Unmatched Expression Brackets
-
-**Frequency**: 6% of validation failures
+## Error #4: Broken Strings & Escaping (JavaScript syntax errors)
**What Happens**:
-- Parsing error during save
-- Error: "Unmatched expression brackets"
-- Code appears correct but fails validation
+- The Code node throws a JavaScript syntax error at execution: "Unexpected token" or "Unexpected end of input"
+- Cause is your own JS β unbalanced quotes or a raw newline inside a plain quoted string
+
+This is a plain JavaScript concern, **not** a validator check. The validator (n8n-mcp β₯ 2.63.0) does not flag balanced apostrophes, `{ }` in regex, or `{{ }}` sitting inside a string literal β those are all valid JavaScript. Only genuinely malformed JS throws, and it throws at runtime.
### The Problem
-This error typically occurs when:
-1. Strings contain unbalanced quotes
-2. Multi-line strings with special characters
-3. Template literals with nested brackets
+This happens when:
+1. A quote inside a same-quoted string is left unescaped
+2. A plain (non-template) string spans multiple lines
+3. Backslashes in paths/regex are not escaped
```javascript
-// β WRONG: Unescaped quote in string
+// β FINE: an apostrophe inside a double-quoted string is valid JavaScript
const message = "It's a nice day";
-// Single quote breaks string
-```
-```javascript
-// β WRONG: Unbalanced brackets in regex
-const pattern = /\{(\w+)\}/; // JSON storage issue
+// β FINE: braces in a regex literal are valid
+const pattern = /\{(\w+)\}/;
```
```javascript
-// β WRONG: Multi-line string with quotes
+// β SYNTAX ERROR: raw newline + unescaped inner double-quotes in a plain string
const html = "
Hello
";
-// Quote balance issues
```
### The Solution
```javascript
-// β CORRECT: Escape quotes
-const message = "It\\'s a nice day";
-// Or use different quotes
-const message = "It's a nice day"; // Double quotes work
+// β Mix quote styles or escape, whichever reads cleaner
+const message = "It's a nice day"; // double quotes around an apostrophe β fine
+const other = 'She said "hello"'; // single quotes around double quotes β fine
```
```javascript
-// β CORRECT: Escape regex properly
-const pattern = /\\{(\\w+)\\}/;
+// β Regex literals need no extra escaping of their own braces
+const pattern = /\{(\w+)\}/;
```
```javascript
@@ -753,12 +737,11 @@ Use this checklist before deploying Code nodes:
- [ ] All code paths return data
### Return Format
-- [ ] Returns array: `[...]`
-- [ ] Each item has `json` property: `{json: {...}}`
-- [ ] Format is `[{json: {...}}]`
+- [ ] Returns items, not a primitive/`null`
+- [ ] Canonical shape `[{json: {...}}]` (bare objects auto-wrap, but be explicit)
### Syntax
-- [ ] No `{{ }}` expression syntax (use JavaScript)
+- [ ] No `{{ }}` written as code (it's for other nodes' fields; in a string it's just literal text)
- [ ] Template literals use backticks: `` `${variable}` ``
- [ ] All quotes and brackets balanced
- [ ] Strings properly escaped
@@ -784,12 +767,12 @@ Use this checklist before deploying Code nodes:
|---------------|--------------|-----|
| "Code cannot be empty" | Empty code field | Add meaningful code |
| "Code must return data" | Missing return statement | Add `return [...]` |
-| "Return value must be an array" | Returning object instead of array | Wrap in `[...]` |
-| "Each item must have json property" | Missing `json` wrapper | Use `{json: {...}}` |
-| "Unexpected token" | Expression syntax `{{ }}` in code | Remove `{{ }}`, use JavaScript |
+| "Code doesn't return items properly" | Returned a primitive (string/number) or `null` | Return `[{json:{...}}]` (objects/arrays auto-wrap; primitives don't) |
+| "Not all items have a json key" | Mixed return β some items wrapped, some bare | Wrap every item: `{json: {...}}` |
+| "Expression syntax {{...}} is not valid in Code nodes" / "Unexpected token" | `{{ }}` written as code (not inside a string) | Use JavaScript: `$json.x` or `` `${$json.x}` `` |
| "Cannot read property X of undefined" | Missing null check | Use optional chaining `?.` |
| "Cannot read property X of null" | Null value access | Add guard clause or default |
-| "Unmatched expression brackets" | Quote/bracket imbalance | Check string escaping |
+| "Unexpected end of input" | Unbalanced quotes/brackets in your JS | Escape strings or use backtick template literals |
| "UnsupportedFunctionError ... httpRequestWithAuthentication" | Auth helper blocked in task runner | Use HTTP Request node + credential, or sub-workflow pattern (Error #6) |
| "$env is not defined" | `N8N_BLOCK_ENV_ACCESS_IN_NODE=true` | Route secrets through credentials, not `$env` (Error #7) |
| "Cannot find module 'crypto'" | `require()` allowlist not set | Move logic out of Code node, or set `N8N_RUNNERS_ALLOWED_BUILT_IN_MODULES` |
@@ -853,17 +836,17 @@ console.log('Input structure:', JSON.stringify(items[0], null, 2));
## Summary
**Top 7 Errors to Avoid**:
-1. **Empty code / missing return** (38%) - Always return data
-2. **Expression syntax `{{ }}`** (8%) - Use JavaScript, not expressions
-3. **Wrong return format** (5%) - Always `[{json: {...}}]`
-4. **Unmatched brackets** (6%) - Escape strings properly
+1. **Empty code / missing return** - Always return data
+2. **Expression syntax as code** - Use JavaScript, not `{{ }}` (in-string `{{ }}` is just literal text)
+3. **Return shape** - prefer `[{json: {...}}]`; primitives/`null` fail (bare objects auto-wrap)
+4. **Broken strings** - unbalanced quotes/brackets throw a JS syntax error; escape or use template literals
5. **Missing null checks** - Use optional chaining `?.`
6. **`httpRequestWithAuthentication` blocked** - Use HTTP Request node + credential
7. **`$env` blocked** - Route secrets through credentials, not env access
**Quick Prevention**:
-- Return `[{json: {...}}]` format
-- Use JavaScript, NOT `{{ }}` expressions
+- Prefer the canonical `[{json: {...}}]` return; never return a primitive or `null`
+- Write JavaScript β don't put `{{ }}` where code belongs
- Check for null/undefined before accessing
- Test with empty and invalid data
- Use browser console for debugging
diff --git a/skills/n8n-code-javascript/README.md b/skills/n8n-code-javascript/README.md
index 858e44cd..ab155c2b 100644
--- a/skills/n8n-code-javascript/README.md
+++ b/skills/n8n-code-javascript/README.md
@@ -194,13 +194,13 @@ const name = $json.body.name;
```
### #2: Return Format
-**CRITICAL**: Must return array with json property
+**Prefer the canonical `[{json: {...}}]`** β unambiguous in both execution modes. A bare object auto-wraps in *Run Once for All Items* mode, so it runs too; what actually fails is returning a primitive (string/number) or `null`.
```javascript
-// β WRONG
+// β οΈ Auto-wrapped in All Items mode β [{json: {result: 'success'}}]. Runs, but prefer the array form.
return {json: {result: 'success'}};
-// β CORRECT
+// β CANONICAL
return [{json: {result: 'success'}}];
```
@@ -290,7 +290,7 @@ const value = $json.field;
### Essential Rules
1. Choose "All Items" mode (recommended)
2. Access data: `$input.all()`, `$input.first()`, `$input.item`
-3. **MUST return**: `[{json: {...}}]` format
+3. **Return** the canonical `[{json: {...}}]` (bare objects auto-wrap in All Items mode; primitives/`null` fail)
4. **Webhook data**: Under `.body` property
5. **No `{{}}` syntax**: Use JavaScript directly
diff --git a/skills/n8n-code-javascript/SKILL.md b/skills/n8n-code-javascript/SKILL.md
index bf5cd7e1..8c9ab950 100644
--- a/skills/n8n-code-javascript/SKILL.md
+++ b/skills/n8n-code-javascript/SKILL.md
@@ -31,9 +31,9 @@ return processed;
1. **Choose "Run Once for All Items" mode** (recommended for most use cases)
2. **Access data**: `$input.all()`, `$input.first()`, or `$input.item`
-3. **CRITICAL**: Must return `[{json: {...}}]` format
+3. **Return `[{json: {...}}]`** β the canonical, mode-portable form. In *Run Once for All Items* mode n8n also auto-wraps a bare `return {β¦}` object, so that runs too; what genuinely fails is returning a primitive (string/number) or `null`.
4. **CRITICAL**: Webhook data is under `$json.body` (not `$json` directly)
-5. **Built-ins available**: `this.helpers.httpRequest()` (no auth β the bare `$helpers` global is **undefined** in the task-runner sandbox, so `$helpers.httpRequest()` throws `ReferenceError: $helpers is not defined`; ignore the validator if it suggests `$helpers`), DateTime (Luxon), $jmespath(). **Not available**: `this.helpers.httpRequestWithAuthentication` (deny-listed), $env (when N8N_BLOCK_ENV_ACCESS_IN_NODE=true), require() (unless allowlisted). For anything beyond a trivial unauthenticated GET (auth, pagination, retries), prefer the **HTTP Request node** and keep Code nodes for pure logic.
+5. **Built-ins available**: `this.helpers.httpRequest()` (no auth β the bare `$helpers` global is **undefined** in the task-runner sandbox, so `$helpers.httpRequest()` throws `ReferenceError: $helpers is not defined`), DateTime (Luxon), $jmespath(). **Not available**: `this.helpers.httpRequestWithAuthentication` (deny-listed), $env (when N8N_BLOCK_ENV_ACCESS_IN_NODE=true), require() (unless allowlisted). For anything beyond a trivial unauthenticated GET (auth, pagination, retries), prefer the **HTTP Request node** and keep Code nodes for pure logic.
6. **Instance-allowlisted libraries**: Self-hosted instances can allowlist modules via `N8N_RUNNERS_ALLOWED_BUILT_IN_MODULES` and `N8N_RUNNERS_ALLOWED_EXTERNAL_MODULES` (legacy: `NODE_FUNCTION_ALLOW_BUILTIN` / `NODE_FUNCTION_ALLOW_EXTERNAL`). If the user says their instance allows specific modules (e.g. `axios`, `lodash`, `crypto`), use them via `require()` β don't refuse. If unsure, ask or default to built-ins only.
7. **Wrong skill?** If you're writing code for a **Custom Code Tool** attached to an AI Agent (`@n8n/n8n-nodes-langchain.toolCode`), stop β that node has a different contract (input via `query`, must return a string, no `$input`/`$helpers`). Use the **n8n-code-tool** skill.
@@ -166,7 +166,9 @@ const name = webhookData.name;
## Return Format Requirements
-**CRITICAL RULE**: Always return array of objects with `json` property
+**Canonical form**: `[{json: {...}}]` β an array of objects each with a `json` property. It is unambiguous and works identically in both execution modes, so make it your default.
+
+In *Run Once for All Items* mode n8n auto-normalizes looser shapes on the way out: a single bare object, or an array of bare objects, gets wrapped under `json` for you. So `return {foo: 1}` runs. What has nothing to wrap β and therefore genuinely fails at runtime with "Code doesn't return items properly" β is a primitive (string/number/boolean) or `null`/`undefined`. (n8n-mcp β₯ 2.63.0 no longer flags a bare-object return as an error; it reflects this auto-wrap behavior.)
### Correct Return Formats
@@ -207,28 +209,32 @@ if (shouldProcess) {
}
```
-### Incorrect Return Formats
+### Non-Canonical Returns (auto-wrapped β prefer the canonical form)
```javascript
-// β WRONG: Object without array wrapper
+// β οΈ Auto-wrapped in All Items mode β [{json: {field: value}}]. Runs, but prefer the array form.
return {
json: {field: value}
};
-// β WRONG: Array without json wrapper
+// β οΈ Auto-wrapped β [{json: {field: value}}]. Runs, but add the json wrapper for clarity.
return [{field: value}];
-// β WRONG: Plain string
-return "processed";
+// β Fine β input items already carry a json property, so returning them unchanged is a valid passthrough
+return $input.all();
+```
-// β WRONG: Raw data without mapping
-return $input.all(); // Missing .map()
+### Genuinely Broken Returns
+
+```javascript
+// β FAILS: primitive β n8n errors "Code doesn't return items properly"
+return "processed";
-// β WRONG: Incomplete structure
-return [{data: value}]; // Should be {json: value}
+// β FAILS: null / undefined β nothing to pass to the next node
+return null;
```
-**Why it matters**: Next nodes expect array format. Incorrect format causes workflow execution to fail.
+**Why it matters**: The canonical `[{json: {...}}]` is unambiguous and behaves the same in both modes. n8n auto-normalizes bare objects and arrays-of-objects in All Items mode, but a primitive or `null` return has nothing to wrap and stops execution.
**See**: [ERROR_PATTERNS.md](ERROR_PATTERNS.md) #3 for detailed error solutions
@@ -255,8 +261,8 @@ The full library covers 10 patterns: multi-source aggregation, regex filtering,
The recurring Code node failures, in rough frequency order:
1. **Empty code / missing return** β always end with `return [...]`, and make sure *every* branch returns.
-2. **Expression syntax in code** β no `{{ }}`. Use JavaScript: `` `${$json.field}` `` or `$input.first().json.field`.
-3. **Wrong return wrapper** β `return {json:{...}}` fails; must be `return [{json:{...}}]`.
+2. **Expression syntax as code** β don't write `{{ }}` where JavaScript belongs (`return {{ $json.x }}` is a syntax error). Use `` `${$json.field}` `` or `$input.first().json.field`. `{{ }}` *inside a string literal* is fine β it's just literal text n8n won't evaluate.
+3. **Return shape** β prefer `return [{json:{...}}]`. A bare `return {β¦}` auto-wraps in All Items mode, but returning a primitive (string/number) or `null` is what actually fails.
4. **Missing null checks** β use optional chaining: `item.json?.user?.email || 'fallback'`.
5. **Webhook body nesting** β `$json.email` is undefined; use `$json.body.email`.
6. **Auth helpers blocked** (`httpRequestWithAuthentication`) and `$env` blocked β route secrets through credentials/HTTP Request node, not the Code node sandbox.
@@ -371,10 +377,10 @@ Consider other nodes when:
Before deploying Code nodes, verify:
- [ ] **Code is not empty** - Must have meaningful logic
-- [ ] **Return statement exists** - Must return array of objects
-- [ ] **Proper return format** - Each item: `{json: {...}}`
+- [ ] **Return statement exists** - Returns items, not a primitive/`null`
+- [ ] **Canonical return format** - Each item: `{json: {...}}` (bare objects auto-wrap, but be explicit)
- [ ] **Data access correct** - Using `$input.all()`, `$input.first()`, or `$input.item`
-- [ ] **No n8n expressions** - Use JavaScript template literals: `` `${value}` ``
+- [ ] **No `{{ }}` written as code** - Use JavaScript template literals: `` `${value}` ``
- [ ] **Error handling** - Guard clauses for null/undefined inputs
- [ ] **Webhook data** - Access via `.body` if from webhook
- [ ] **Mode selection** - "All Items" for most cases
diff --git a/skills/n8n-expression-syntax/COMMON_MISTAKES.md b/skills/n8n-expression-syntax/COMMON_MISTAKES.md
index dba879b8..84a70d0a 100644
--- a/skills/n8n-expression-syntax/COMMON_MISTAKES.md
+++ b/skills/n8n-expression-syntax/COMMON_MISTAKES.md
@@ -312,24 +312,32 @@ path: "user-webhook/:userId" // Use dynamic URL parameters instead
---
-## 14. String Concatenation Confusion
+## 14. Template Literals & Concatenation Outside `{{ }}`
-**Problem**: Attempting JavaScript template literals
+**Problem**: A backtick template literal or `+` concatenation shows up as literal text
-β **Wrong**:
+β **Wrong** (written as the whole field value, with no `{{ }}`):
```
-`Hello ${$json.name}!` // Template literal syntax
-"Hello " + $json.name + "!" // String concatenation
+`Hello ${$json.name}!` // bare backticks β printed verbatim
+"Hello " + $json.name + "!" // bare concatenation β printed verbatim
```
-β **Correct**:
+β **Correct** (any of these):
+```
+Hello {{$json.name}}! // adjacent text + {{ }} auto-concatenate
+{{ `Hello ${$json.name}!` }} // a template literal INSIDE {{ }}
+{{ "Hello " + $json.name + "!" }} // + concatenation INSIDE {{ }}
+```
+
+**Why it fails**: n8n only evaluates what's inside `{{ }}`; everything else is literal text. The backticks and `+` aren't the problem β the **missing `{{ }}`** is. **Inside** an expression, backtick template literals with `${...}` interpolation are fully supported modern JavaScript, so this is valid and evaluates:
+
```
-Hello {{$json.name}}! // n8n expressions auto-concatenate
+={{ $json.items.map(i => `${i.name} β ${i.qty}`).join(', ') }}
```
-**Why it fails**: n8n expressions don't use JavaScript template literal syntax. Adjacent text and expressions are automatically concatenated.
+The same holds for optional chaining (`{{ $json.user?.email }}`) and string-keyed bracket access (`{{ $json['some-prop'] }}`) β both are valid n8n expressions. As of n8n-mcp β₯ 2.63.0 the validator no longer flags template literals, optional chaining, or bracket access inside expressions (earlier versions raised false-positive errors on them).
-**How to identify**: Literal backticks or + symbols appear in output.
+**How to identify**: Literal backticks or `+` symbols appear in output β the code wasn't wrapped in `{{ }}`.
---
@@ -371,7 +379,7 @@ Hello {{$json.name}}! // n8n expressions auto-concatenate
| = in text | Literal = | Remove = prefix |
| Dynamic path | Doesn't work | Use static path |
| Missing .json | Undefined | Add .json |
-| Template literals | Literal text | Use {{ }} |
+| Template literal / `+` outside {{ }} | Literal text | Wrap in {{ }} (both work inside) |
| Empty {{ }} | Literal braces | Add expression |
---
diff --git a/skills/n8n-mcp-tools-expert/SKILL.md b/skills/n8n-mcp-tools-expert/SKILL.md
index 5c14c583..0e4a3c5a 100644
--- a/skills/n8n-mcp-tools-expert/SKILL.md
+++ b/skills/n8n-mcp-tools-expert/SKILL.md
@@ -17,11 +17,10 @@ n8n-mcp provides tools organized into categories:
2. **Configuration Validation** β [VALIDATION_GUIDE.md](VALIDATION_GUIDE.md)
3. **Workflow Management** β [WORKFLOW_GUIDE.md](WORKFLOW_GUIDE.md)
4. **Template Library** - Search and deploy 2,700+ real workflows
-5. **Workflow Generation** - Natural-language β workflow with proposal review (`n8n_generate_workflow`, hosted-only)
-6. **Data Tables** - Manage n8n data tables and rows (`n8n_manage_datatable`)
-7. **Credential Management** - Full credential CRUD + schema discovery (`n8n_manage_credentials`)
-8. **Security & Audit** - Instance security auditing with custom deep scan (`n8n_audit_instance`)
-9. **Documentation & Guides** - Tool docs, AI agent guide, Code node guides
+5. **Data Tables** - Manage n8n data tables and rows (`n8n_manage_datatable`)
+6. **Credential Management** - Full credential CRUD + schema discovery (`n8n_manage_credentials`)
+7. **Security & Audit** - Instance security auditing with custom deep scan (`n8n_audit_instance`)
+8. **Documentation & Guides** - Tool docs, AI agent guide, Code node guides
---
@@ -38,7 +37,6 @@ n8n-mcp provides tools organized into categories:
| `n8n_update_partial_workflow` | Editing workflows (MOST USED!) | 50-200ms |
| `validate_workflow` | Checking complete workflow | 100-500ms |
| `n8n_deploy_template` | Deploy template to n8n instance | 200-500ms |
-| `n8n_generate_workflow` | NL β workflow (proposals β deploy), hosted-only | 2-15s |
| `n8n_manage_datatable` | Managing data tables and rows | 50-500ms |
| `n8n_manage_credentials` | Credential CRUD + schema discovery | 50-500ms |
| `n8n_audit_instance` | Security audit (built-in + custom scan) | 500-5000ms |
@@ -225,7 +223,7 @@ See [WORKFLOW_GUIDE.md](WORKFLOW_GUIDE.md) for:
- Smart parameters (branch, case)
- AI connection types (8 types)
- Workflow activation (activateWorkflow/deactivateWorkflow)
-- n8n_deploy_template, n8n_generate_workflow
+- n8n_deploy_template
- n8n_workflow_versions
- n8n_manage_credentials (credential CRUD + schema discovery)
- n8n_audit_instance (security auditing)
@@ -246,16 +244,6 @@ See [OPERATIONS_GUIDE.md](OPERATIONS_GUIDE.md) for full search/get/deploy exampl
---
-## Workflow Generation
-
-`n8n_generate_workflow` turns a natural-language description into a workflow via a review checkpoint. **Hosted-only** β self-hosted gets `{hosted_only: true}` with a redirect (fall back to `n8n_deploy_template` or `n8n_create_workflow`). Two paths: **Path A** (default) returns up to 5 proposals, then deploy one with `deploy_id`; **Path B** uses `skip_cache: true` for a fresh preview, then `confirm_deploy: true`. Deployed workflows are **inactive** (configure credentials in UI first); always `n8n_validate_workflow` after. More specific descriptions (trigger type, named services, logic/flow) yield better results.
-
-**When to use which:** `n8n_deploy_template` (curated library) Β· `n8n_generate_workflow` (plain English, hosted only) Β· `n8n_create_workflow` (node-by-node control).
-
-See [WORKFLOW_GUIDE.md](WORKFLOW_GUIDE.md) for both paths, parameters, and pitfalls in full.
-
----
-
## Data Table Management
`n8n_manage_datatable` is the MCP tool for managing data tables and rows from *outside* a workflow (table actions `createTable`/`listTables`/`getTable`/`updateTable`/`deleteTable`; row actions `getRows`/`insertRows`/`updateRows`/`upsertRows`/`deleteRows`, with filtering, pagination, and `dryRun`). Don't confuse it with the in-workflow `nodes-base.dataTable` node, which reads/writes rows *during execution* (see [n8n-node-configuration β OPERATION_PATTERNS.md](../n8n-node-configuration/OPERATION_PATTERNS.md#data-table-nodes-basedatatable)). Rule of thumb: MCP tool to set up a table once, workflow node to read/write on every execution. `deleteRows` requires a filter; use `dryRun: true` before bulk changes.
diff --git a/skills/n8n-mcp-tools-expert/VALIDATION_GUIDE.md b/skills/n8n-mcp-tools-expert/VALIDATION_GUIDE.md
index 4534eaf2..09158ace 100644
--- a/skills/n8n-mcp-tools-expert/VALIDATION_GUIDE.md
+++ b/skills/n8n-mcp-tools-expert/VALIDATION_GUIDE.md
@@ -63,27 +63,39 @@ validate_node({
## Validation Profiles
-Choose based on your stage:
-
-**minimal** - Only required fields
-- Fastest
-- Most permissive
-- Use: Quick checks during editing
-
-**runtime** - Values + types (**RECOMMENDED**)
-- Balanced validation
-- Catches real errors
+The profile controls **which advisory findings you see** β not how likely the validator is to be
+wrong. What flips `valid: false` (real errors) and what counts as a security/deprecation warning is
+the same under every profile; the profiles differ only in how many *best-practice advisories* ride
+along (n8n-mcp β₯ 2.63.0). Choose by how much lint you want at this stage:
+
+**minimal** - Required fields only
+- Fastest, leanest output
+- Errors for missing required fields; nothing advisory
+- Use: Quick checks while you are still assembling a config
+
+**runtime** - Errors + security/deprecation warnings (**RECOMMENDED**)
+- The profile that decides valid/invalid for deployment
+- Real value/type errors, plus warnings that matter for safety (security, deprecated nodes)
+- Stays signal-heavy: no per-node best-practice warnings and no outdated-`typeVersion` notes
+ (at most a single top-level "add error handling" suggestion)
- Use: Pre-deployment validation
-**ai-friendly** - Reduce false positives
-- For AI-generated configs
-- Tolerates minor issues
-- Use: When AI configures nodes
+**ai-friendly** - runtime + best-practice advisories
+- Everything `runtime` reports, *plus* advisory notes: outdated-`typeVersion` suggestions,
+ per-node "without error handling" warnings, resource-locator `cachedResultName` advice,
+ long-chain hints
+- These advisories are informational β they never flip `valid: false`
+- Use: Reviewing an AI-built or hand-built workflow for polish
+
+**strict** - ai-friendly + strict-only checks
+- Everything `ai-friendly` reports, plus checks like "Property 'X' won't be used" for
+ leftover parameters hidden by the current settings
+- The most verbose profile β expect the most advisory notes, not more real errors
+- Use: A final lint pass before shipping
-**strict** - Maximum validation
-- Strictest rules
-- May have false positives
-- Use: Production deployment
+> These are advisory tiers, not accuracy tiers. `ai-friendly` is **not** "more tolerant" and
+> `strict` is **not** "more likely to be wrong" β a config that is `valid` under `runtime` stays
+> `valid` under `strict`; `strict` just adds more suggestions and warnings to read.
---
@@ -123,11 +135,15 @@ Choose based on your stage:
### Error Types
-- `missing_required` - Must fix
-- `invalid_value` - Must fix
-- `type_mismatch` - Must fix
-- `best_practice` - Should fix (warning)
-- `suggestion` - Optional improvement
+- `missing_required` - Must fix (flips `valid:false`)
+- `invalid_value` - Must fix (flips `valid:false`)
+- `type_mismatch` - Must fix (flips `valid:false`)
+- `best_practice` - Advisory warning; surfaces under `ai-friendly`/`strict` (security/deprecation warnings surface under every profile)
+- `suggestion` - Optional improvement; surfaces under `ai-friendly`/`strict`
+
+The `best_practice` warning shown above (rate-limit / error-handling advice) is one of the
+advisories gated to `ai-friendly`/`strict` β under `runtime` this same config reports the error
+with no such warning.
---
@@ -317,37 +333,48 @@ n8n_autofix_workflow({
```
1. Read error message carefully
-2. Check if it's a known false positive
-3. Fix real errors
+2. Separate real errors (they flip valid:false) from advisory notes
+3. Fix the errors
4. Validate again
5. Iterate until clean
```
+Only findings in `errors` flip `valid: false`. `warnings` and `suggestions` are advice β an
+outdated-`typeVersion` note or a "without error handling" suggestion under `ai-friendly`/`strict`
+does not make the workflow invalid, so treat them as a to-review list, not a blocker.
+
### Common Errors
-**"Required field missing"**
-β Add the field with appropriate value
+**"Required field missing"** / **"Required property 'X' cannot be empty"**
+β Add the field with a real value. This is a true error even when the field looks optional β n8n's
+own publish validation rejects the same empty value, so the workflow will not save.
**"Invalid value"**
-β Check allowed values in get_node output
+β Check allowed values in get_node output. Fires only on an explicitly wrong enum value; an
+*omitted* operation on a multi-resource node (Gmail, Slack, Telegramβ¦) is no longer flagged
+(n8n-mcp β₯ 2.63.0 resolves the correct per-resource default before checking).
**"Type mismatch"**
β Convert to correct type (string/number/boolean)
-**"Cannot have singleValue"**
-β Auto-sanitization will fix on next update
-
-**"Missing operator metadata"**
-β Auto-sanitization will fix on next update
+**"Code cannot be empty"**
+β Fill in the Code node's `jsCode`/`pythonCode`. Kept as a true error β n8n refuses to run an
+empty Code node.
-### False Positives
+> Operator shapes (`singleValue`, IF/Switch `conditions.options` metadata) are **no longer
+> validation errors**. The save-time sanitizer normalizes them (see Auto-Sanitization), and
+> validate-only calls leave them alone β so you will not see "Cannot have singleValue" or
+> "Missing operator metadata" from the validator anymore.
-Some validation warnings may be acceptable:
-- Optional best practices
-- Node-specific edge cases
-- Profile-dependent issues
+### Errors vs advisories
-Use **ai-friendly** profile to reduce false positives.
+There is no standing list of validator false positives to ignore (n8n-mcp β₯ 2.63.0 removed the
+classes that used to require it β template literals inside `{{ }}`, optional chaining `?.`,
+string-keyed bracket access like `$json['some-prop']`, legacy IF v1 condition shapes, the
+Webhook β Respond-to-Webhook pattern, and outdated-but-supported typeVersions all validate cleanly
+now). If something lands in `errors`, treat it as real. If you want the leanest output while
+building, validate under `runtime` (errors + security/deprecation only); switch to
+`ai-friendly`/`strict` when you want the best-practice advisories.
---
@@ -368,7 +395,7 @@ Use **ai-friendly** profile to reduce false positives.
- Skip validation before deployment
- Ignore error messages
-- Use strict profile during development (too many warnings)
+- Use strict profile mid-build (it layers on best-practice advisories that are noise until the workflow is nearly done)
- Assume validation passed (check result)
- Try to manually fix auto-sanitization issues
@@ -486,13 +513,13 @@ get_node({nodeType: "nodes-base.slack", detail: "standard"})
### Mistake 3: Not Using Validation Profiles
-**Problem**: Too many false positives OR missing real errors
+**Problem**: Missing real errors, or drowning in advisory notes at the wrong stage
-**Profiles**:
-- `minimal` - Only required fields (fast, permissive)
-- `runtime` - Values + types (recommended for pre-deployment)
-- `ai-friendly` - Reduce false positives (for AI configuration)
-- `strict` - Maximum validation (for production)
+**Profiles** (they gate advisory volume, not accuracy β see [Validation Profiles](#validation-profiles)):
+- `minimal` - Required fields only (fast, leanest)
+- `runtime` - Errors + security/deprecation warnings (recommended for pre-deployment; decides valid/invalid)
+- `ai-friendly` - runtime + best-practice advisories (outdated-typeVersion, error-handling suggestionsβ¦)
+- `strict` - ai-friendly + strict-only checks (e.g. "property won't be used")
```javascript
// WRONG - Uses default profile
diff --git a/skills/n8n-mcp-tools-expert/WORKFLOW_GUIDE.md b/skills/n8n-mcp-tools-expert/WORKFLOW_GUIDE.md
index 1eaba05f..62f58593 100644
--- a/skills/n8n-mcp-tools-expert/WORKFLOW_GUIDE.md
+++ b/skills/n8n-mcp-tools-expert/WORKFLOW_GUIDE.md
@@ -436,112 +436,6 @@ const result = n8n_deploy_template({
---
-## n8n_generate_workflow (NATURAL LANGUAGE β WORKFLOW)
-
-**Speed**: Proposals ~2s, fresh generation 5β15s, deploy ~3s
-
-**Use when**: User describes the workflow in plain English and wants the system to draft (and optionally deploy) it.
-
-> β οΈ **Hosted-only feature.** On self-hosted instances the tool returns `{hosted_only: true}` with a redirect message rather than a workflow. For self-hosted, use `n8n_deploy_template` (templates) or `n8n_create_workflow` (manual).
-
-### How It Works
-
-It's a **multi-step flow with a review checkpoint** β proposals/preview are returned first, deployment requires a second call. This avoids deploying low-quality drafts.
-
-### Path A: Proposals β Deploy (default, recommended)
-
-```javascript
-// Step 1: Generate proposals (NOT deployed, returns up to 5 candidates)
-n8n_generate_workflow({
- description: "Slack daily standup reminder at 9am every weekday"
-})
-// β {
-// status: "proposals",
-// proposals: [
-// {
-// id: "uuid-1",
-// name: "Daily Standup Reminder",
-// description: "...",
-// flow_summary: "Schedule trigger β Slack message",
-// credentials_needed: ["slackApi"]
-// },
-// ...
-// ]
-// }
-
-// Step 2: Deploy the proposal you picked
-n8n_generate_workflow({
- description: "Slack daily standup reminder at 9am every weekday",
- deploy_id: "uuid-1"
-})
-// β { status: "deployed", workflow_id, workflow_name, workflow_url,
-// node_count, node_summary }
-```
-
-### Path B: Skip Cache β Preview β Confirm
-
-Use when none of the proposals match what you want.
-
-```javascript
-// Step 1: Bypass proposals; get a fresh preview (NOT deployed)
-n8n_generate_workflow({
- description: "Webhook receives JSON, transforms it, POSTs to a REST API",
- skip_cache: true
-})
-// β { status: "preview", ... }
-
-// Step 2: Deploy the preview
-n8n_generate_workflow({
- description: "Webhook receives JSON, transforms it, POSTs to a REST API",
- confirm_deploy: true
-})
-// β { status: "deployed", ... }
-```
-
-### Writing a Good Description
-
-The quality of the generated workflow is bound by the clarity of the description. Always include:
-
-- **Trigger type**: `webhook`, `schedule` (with cadence β "every 15 min", "weekdays at 9am"), `manual`, `form`, `chat`
-- **Services involved**: name them explicitly (Slack, Gmail, HubSpot, Postgres, etc.) β generic terms ("a chat tool") yield generic results
-- **Logic / flow**: branches, transforms, aggregation, deduplication, retry behavior
-
-**Bad**: `"Send a notification when something happens"`
-
-**Good**: `"When a new row is added to the 'leads' Postgres table, enrich it with Clearbit, then post a summary to the #sales Slack channel. Skip rows where 'company' is empty."`
-
-### Parameters
-
-| Parameter | Type | Use |
-|-----------|------|-----|
-| `description` | string (required) | Natural-language description |
-| `deploy_id` | string | ID of a proposal from a prior call β deploys it |
-| `skip_cache` | boolean | Skip proposals; generate from scratch and return a preview |
-| `confirm_deploy` | boolean | Deploy the most recent preview from this session |
-
-### Common Pitfalls
-
-- **Hosted-only** β on self-hosted, no workflow is generated; fall back to `n8n_deploy_template` or `n8n_create_workflow`
-- **Proposals are NOT deployed** β until you call again with `deploy_id` or `confirm_deploy`, nothing exists in n8n
-- **Inactive on deploy** β generated workflows are created in **inactive** state; credentials must be configured in the n8n UI before activation
-- **Per-session state** β pending proposals/preview live in MCP-session state; reconnecting loses them, and you'll need to re-issue the description
-
-### Recommended Follow-Up
-
-Always validate after deploying:
-```javascript
-n8n_generate_workflow({description: "...", deploy_id: "uuid-1"})
-// β workflow_id: "abc"
-
-n8n_validate_workflow({id: "abc"})
-// β catches any node-version or connection issues the generator missed
-
-// If issues found, n8n_autofix_workflow can resolve common ones
-n8n_autofix_workflow({id: "abc", applyFixes: true})
-```
-
----
-
## n8n_workflow_versions (VERSION CONTROL)
**Use when**: Managing workflow history, rollback, cleanup
diff --git a/skills/n8n-validation-expert/ERROR_CATALOG.md b/skills/n8n-validation-expert/ERROR_CATALOG.md
index e2f2c892..833bf8f6 100644
--- a/skills/n8n-validation-expert/ERROR_CATALOG.md
+++ b/skills/n8n-validation-expert/ERROR_CATALOG.md
@@ -15,7 +15,9 @@ Common validation errors by priority:
| type_mismatch | Medium | Error | β |
| invalid_expression | Medium | Error | β |
| invalid_reference | Low | Error | β |
-| operator_structure | Lowest | Warning | β |
+| operator_structure | Lowest | Not flagged | β normalized on save |
+
+> **operator_structure** is no longer a validation finding (n8n-mcp β₯ 2.63.0). n8n derives unary/binary operators from the operator name and defaults the metadata, so both the raw and normalized shapes validate clean; the sanitizer just tidies the canonical form on save. See section 9.
---
@@ -30,7 +32,7 @@ Common validation errors by priority:
- Copying configurations between different operations
- Switching operations that have different requirements
-**Most common validation error**
+**Most common validation error.** In practice the message reads **`Required property 'X' cannot be empty`** (e.g. `Required property 'URL' cannot be empty`, or `Code cannot be empty` for an empty Code node) β using the field's display name. This is a genuine error under every profile: n8n's own publish validation rejects these identically. It is *not* a false positive, even on templates where the field was stripped.
#### Example 1: Slack Channel Missing
@@ -184,6 +186,8 @@ const info = get_node({
**Second most common error**
+> **This fires only for an *explicitly wrong* value.** Omitting `operation` on a multi-resource node (Gmail, Telegram, Slack, Google Drive, Discord, Notion, β¦) no longer produces a fabricated "Invalid value for 'operation'" error (n8n-mcp β₯ 2.63.0) β the validator now resolves the correct per-resource default first. Expression values (`=...`) and dynamically-loaded option lists are also skipped. Under the `minimal` profile the operation enum check is not run at all.
+
#### Example 1: Invalid Operation
**Error**:
@@ -454,38 +458,38 @@ const info = get_node({
**Related**: See **n8n Expression Syntax** skill for comprehensive expression guidance
-#### Example 1: Missing Curly Braces
+> **Static validation catches expression *format*, not *resolution*.** `validate_node` / `validate_workflow` flag a missing `=` prefix (error) and a bare unwrapped `$json` reference (warning). They do **not** resolve node names or property paths *inside* an expression β a typo'd `$('Node')` reference or a missing property (Examples 2β4 below) surfaces at *execution* time, not during validation. Backtick template literals with `${...}` interpolation inside `{{ }}` are valid and are **not** flagged (n8n-mcp β₯ 2.63.0).
+
+#### Example 1: Missing `=` Prefix
+
+An expression field whose value contains `{{ }}` but doesn't start with `=` is treated as literal text β this is a real, static error.
**Error**:
```json
{
- "type": "invalid_expression",
- "property": "text",
- "message": "Expressions must be wrapped in {{}}",
- "current": "$json.name"
+ "type": "error",
+ "property": "assignments.assignments[0].value",
+ "message": "Expression requires = prefix to be evaluated",
+ "current": "{{ $json.name }}"
}
```
**Broken Configuration**:
```javascript
{
- "resource": "message",
- "operation": "post",
- "channel": "#general",
- "text": "$json.name" // β Missing {{}}
+ "text": "{{ $json.name }}" // β Has braces but no leading =
}
```
**Fix**:
```javascript
{
- "resource": "message",
- "operation": "post",
- "channel": "#general",
- "text": "={{$json.name}}" // β Wrapped in {{}}
+ "text": "={{ $json.name }}" // β Leading = makes it an expression
}
```
+> A *bare* reference with no braces at all β `"$json.name"` β is a **warning**, not an error ("possible unwrapped expression"); n8n treats it as literal text, so wrap it as `={{ $json.name }}` if you meant to evaluate it. `n8n_autofix_workflow` can add the missing `=` for you.
+
#### Example 2: Invalid Node Reference
**Error**:
@@ -583,6 +587,8 @@ const info = get_node({
**Less common error**
+> **Which of these validation actually catches**: a broken *connection* to a missing node (Example 2) is a hard error under every profile β `Connection to non-existent node: "X" from "Y"`. A node reference *inside an expression* (Examples 1 & 3, e.g. `$('Old Name')`) is **not** statically resolved β it fails at execution, not during validation. Fix expression references by hand or with the n8n Expression Syntax skill; fix connection references with `cleanStaleConnections`.
+
#### Example 1: Deleted Node Reference
**Error**:
@@ -672,7 +678,7 @@ n8n_update_partial_workflow({
**What it means**: Configuration works but doesn't follow best practices
-**Severity**: Warning (doesn't block execution)
+**Severity**: Warning (doesn't block execution) β surfaces under `ai-friendly` / `strict` only (n8n-mcp β₯ 2.63.0). Error-handling *style* is never a hard error.
**When acceptable**: Development, testing, simple workflows
@@ -700,15 +706,16 @@ n8n_update_partial_workflow({
}
```
-**Recommended Fix**:
+**Recommended Fix** (modern `onError`, set at node level):
```javascript
{
- "resource": "message",
- "operation": "post",
- "channel": "#alerts",
- "continueOnFail": true,
- "retryOnFail": true,
- "maxTries": 3
+ "onError": "continueRegularOutput", // prefer this over deprecated continueOnFail
+ "retryOnFail": true, // maxTries defaults to 3 β stating it isn't required
+ "parameters": {
+ "resource": "message",
+ "operation": "post",
+ "channel": "#alerts"
+ }
}
```
@@ -732,34 +739,31 @@ n8n_update_partial_workflow({
### 7. deprecated
-**What it means**: Using old API version or deprecated feature
+**What it means**: Using a genuinely deprecated feature (e.g. `continueOnFail: true` β the validator suggests `onError: 'continueRegularOutput'` instead)
-**Severity**: Warning (still works but may stop working in future)
+**Severity**: Warning (still works but may stop working in future) β surfaces under every profile
**When to fix**: Always (eventually)
-#### Example 1: Old typeVersion
+#### Example 1: Outdated typeVersion (suggestion, not a deprecation)
-**Warning**:
-```json
-{
- "type": "deprecated",
- "property": "typeVersion",
- "message": "typeVersion 1 is deprecated for Slack node, use version 2",
- "current": 1,
- "recommended": 2
-}
+Running an older-but-supported `typeVersion` is **normal, supported n8n behavior** β the vast majority of production workflows do. It is now a **suggestion** surfaced only under `ai-friendly` / `strict` (n8n-mcp β₯ 2.63.0), not a deprecation warning, and never an error. A `typeVersion` that *never existed* on a core node is still a hard error (it fails activation).
+
+**Suggestion** (plain-string, `ai-friendly` / `strict`):
+```
+Outdated typeVersion for node "Slack": 1. Latest is 2.3.
```
-**Fix**:
+**Fix** (optional β only if you want the newer node behavior):
```javascript
{
"type": "n8n-nodes-base.slack",
- "typeVersion": 2, // β Updated
- // May need to update configuration for new version
+ "typeVersion": 2.3, // β Updated β may need config changes for the new version
}
```
+> `n8n_autofix_workflow` offers `typeversion-upgrade` (to latest, with migration) and `typeversion-correction` (downgrade an *unsupported* version to a real one).
+
---
### 8. performance
@@ -798,15 +802,17 @@ SELECT * FROM users WHERE active = true LIMIT 1000
### 9. operator_structure
-**What it means**: IF/Switch operator structure issues
+**What it means**: The exact shape of an IF/Switch/Filter condition (`singleValue`, `conditions.options` metadata)
-**Severity**: Warning
+**Severity**: Not a validation finding (n8n-mcp β₯ 2.63.0)
+
+**Auto-Fix**: β Normalized automatically on workflow save
-**Auto-Fix**: β YES - Fixed automatically on workflow save
+Validation **accepts** a condition whether or not `singleValue` and the `conditions.options` sub-fields are present β n8n derives unary-ness from the operator name and defaults the metadata. The sanitizer still tidies these into the canonical form on save, so you never need to hand-write them either way. The before/after below is what the sanitizer produces, **not** something the validator errors on.
-**Rare** (mostly auto-fixed)
+**Still real errors** (these are *not* auto-fixed β they change behavior): a legacy v1 operator name (e.g. `smaller`) inside a v2 structure (`"Operation 'smaller' is not valid for type 'string'"`), a v1-shaped `conditions` object on a v2 node (silently evaluates true), and a filter object with no conditions at all.
-#### Fixed Automatically: Binary Operators
+#### Normalized on Save: Binary Operators
**Before** (you create this):
```javascript
@@ -828,7 +834,7 @@ SELECT * FROM users WHERE active = true LIMIT 1000
**You don't need to do anything** - this is fixed on save!
-#### Fixed Automatically: Unary Operators
+#### Normalized on Save: Unary Operators
**Before**:
```javascript
diff --git a/skills/n8n-validation-expert/FALSE_POSITIVES.md b/skills/n8n-validation-expert/FALSE_POSITIVES.md
index 467aabda..4df459e3 100644
--- a/skills/n8n-validation-expert/FALSE_POSITIVES.md
+++ b/skills/n8n-validation-expert/FALSE_POSITIVES.md
@@ -6,13 +6,18 @@ When validation warnings are acceptable and how to handle them.
## What Are False Positives?
-**Definition**: Validation warnings that are technically "issues" but acceptable in your specific use case.
+**Definition**: A validation warning that flags a real trade-off but is acceptable in your specific use case β not something the validator got *wrong*.
-**Key insight**: Not all warnings need to be fixed!
+**Key insight**: Not every warning needs a fix, but the reason has changed (n8n-mcp β₯ 2.63.0).
-Many warnings are context-dependent:
-- ~40% of warnings are acceptable in specific use cases
-- Using `ai-friendly` profile reduces false positives by 60%
+The validator used to emit a large family of *genuine* false positives β warnings and even hard errors on configurations that run fine in production (template literals inside expressions, optional chaining, omitted-operation defaults, the Webhook β Respond-to-Webhook pattern, IF/Filter legacy shapes, and more). Those have been fixed at the source. The validator no longer flags them at all, so there is no longer a standing list of "known false positives to ignore."
+
+What remains is not noise-to-suppress but **context-dependent advice**. Every warning you now see falls into one of two buckets:
+
+- **Security and deprecation warnings** β surfaced under *every* profile (`minimal` through `strict`). Treat these as real.
+- **Best-practice advisories** β error-handling suggestions, rate-limit notes, outdated-`typeVersion` suggestions, `cachedResultName` advice, long-chain hints. These surface **only under `ai-friendly` and `strict`**. `minimal` and `runtime` never emit them.
+
+So the practical question is no longer "is this a false positive?" but "does this best-practice advisory apply to *my* workflow?" The per-case guidance below (error handling, retries, rate limiting, unbounded queries, input validation, credentials) is exactly that judgement call.
---
@@ -20,19 +25,19 @@ Many warnings are context-dependent:
### β Good Practice
```
-1. Run validation with 'runtime' profile
+1. Validate with 'runtime' (errors + security/deprecation only)
2. Fix all ERRORS
-3. Review each WARNING
-4. Decide if acceptable for your use case
-5. Document why you accepted it
+3. Run 'ai-friendly' or 'strict' to surface best-practice advisories
+4. Review each advisory against your use case (this document)
+5. Document why you accepted the ones you skip
6. Deploy with confidence
```
### β Bad Practice
```
-1. Ignore all warnings blindly
-2. Use 'minimal' profile to avoid warnings
-3. Deploy without understanding risks
+1. Ignore security and deprecation warnings
+2. Treat every 'strict' advisory as a mandatory fix (or as noise to blank-ignore)
+3. Deploy without reading the errors
```
---
@@ -41,15 +46,17 @@ Many warnings are context-dependent:
### 1. Missing Error Handling
-**Warning**:
+**Warning** (surfaces under `ai-friendly` / `strict` only):
```json
{
- "type": "best_practice",
- "message": "No error handling configured",
- "suggestion": "Add continueOnFail: true and retryOnFail: true"
+ "type": "warning",
+ "nodeName": "HTTP Request",
+ "message": "HTTP Request node without error handling. Consider adding \"onError: 'continueRegularOutput'\" for non-critical requests or \"retryOnFail: true\" for transient failures."
}
```
+This is never a hard error β error-handling *style* does not block execution or activation (n8n-mcp β₯ 2.63.0). Under `minimal` and `runtime` you will not see it at all.
+
#### When Acceptable
**β Development/Testing Workflows**
@@ -119,19 +126,19 @@ Many warnings are context-dependent:
}
```
-**Fix**:
+**Fix** (modern `onError`, set at node level):
```javascript
{
+ "onError": "continueRegularOutput", // or wire "continueErrorOutput" to a real handler
+ "retryOnFail": true, // maxTries defaults to 3 β no need to state it
"parameters": {
- "query": "INSERT INTO orders...",
- "continueOnFail": true,
- "retryOnFail": true,
- "maxTries": 3,
- "waitBetweenTries": 1000
+ "query": "INSERT INTO orders..."
}
}
```
+> Prefer `onError` over the legacy `continueOnFail: true` β the validator flags `continueOnFail` as deprecated and n8n's UI no longer surfaces it cleanly. And note: if you set `onError: 'continueErrorOutput'` you must wire the node's error output (`main[1]`) to a handler, or failed items are silently dropped β the validator warns about exactly that (n8n-mcp β₯ 2.63.0).
+
**β Critical Integrations**
```javascript
// BAD: Payment processing without error handling
@@ -528,49 +535,42 @@ Many warnings are context-dependent:
### Strategy 1: Progressive Strictness
-**Development**:
+The profiles are cumulative β each surfaces everything the lower one does, plus more (n8n-mcp β₯ 2.63.0). Move up the ladder as a workflow gets closer to production.
+
+**While editing** β fast, errors only:
```javascript
-validate_node({
- nodeType: "nodes-base.slack",
- config,
- profile: "ai-friendly" // Fewer warnings during development
-})
+validate_node({ nodeType: "nodes-base.slack", config, profile: "runtime" })
+// errors + security/deprecation warnings; no best-practice advisories
```
-**Pre-Production**:
+**Before deploying** β surface the advisories you may want to act on:
```javascript
-validate_node({
- nodeType: "nodes-base.slack",
- config,
- profile: "runtime" // Balanced validation
-})
+validate_node({ nodeType: "nodes-base.slack", config, profile: "ai-friendly" })
+// adds error-handling suggestions, rate-limit notes, outdated-typeVersion suggestions
```
-**Production Deployment**:
+**Hardening a critical workflow** β the full lint:
```javascript
-validate_node({
- nodeType: "nodes-base.slack",
- config,
- profile: "strict" // All warnings, review each one
-})
+validate_node({ nodeType: "nodes-base.slack", config, profile: "strict" })
+// everything ai-friendly emits, plus "property won't be used" leftover checks
```
### Strategy 2: Profile by Workflow Type
**Quick Automations**:
-- Profile: `ai-friendly`
-- Accept: Most warnings
-- Fix: Only errors + security warnings
+- Profile: `runtime`
+- See: errors + security/deprecation warnings
+- Fix: errors; skip best-practice advisories you don't need
**Business-Critical Workflows**:
- Profile: `strict`
-- Accept: Very few warnings
-- Fix: Everything possible
+- See: every advisory
+- Fix: errors, security, and any advisory that applies to a production path
**Integration Testing**:
- Profile: `minimal`
-- Accept: All warnings (just testing connections)
-- Fix: Only errors that prevent execution
+- See: only errors that would stop execution
+- Fix: those errors; everything else is out of scope while wiring connections
---
@@ -634,67 +634,27 @@ When accepting a warning, document why:
---
-## Known n8n Issues
-
-### Issue #304: IF Node Metadata Warning
-
-**Warning**:
-```json
-{
- "type": "metadata_incomplete",
- "message": "IF node missing conditions.options metadata",
- "node": "IF"
-}
-```
-
-**Status**: False positive for IF v2.2+
-
-**Why it occurs**: Auto-sanitization adds metadata, but validation runs before sanitization
-
-**What to do**: Ignore - metadata is added on save
-
-### Issue #306: Switch Branch Count
-
-**Warning**:
-```json
-{
- "type": "configuration_mismatch",
- "message": "Switch has 3 rules but 4 output connections",
- "node": "Switch"
-}
-```
-
-**Status**: False positive when using "fallback" mode
-
-**Why it occurs**: Fallback creates extra output
-
-**What to do**: Ignore if using fallback intentionally
-
-### Issue #338: Credential Validation in Test Mode
-
-**Warning**:
-```json
-{
- "type": "credentials_invalid",
- "message": "Cannot validate credentials without execution context"
-}
-```
+## What the validator no longer flags
-**Status**: False positive during static validation
+Earlier versions of this guide listed "known n8n issues" to ignore. Those false positives are gone at the source (n8n-mcp β₯ 2.63.0) β the validator simply does not emit them anymore, so there is nothing to recognize or suppress. If you are on an older server and still see them, upgrading is the fix. Among the classes that no longer fire:
-**Why it occurs**: Credentials validated at runtime, not build time
+- **Template literals inside expressions** β `` ={{ `https://api/${$json.id}` }} `` is valid; n8n's engine evaluates full modern JS (template literals, optional chaining `?.`) inside `{{ }}`.
+- **Omitted `operation` on multi-resource nodes** (Gmail, Telegram, Slack, Google Drive, Discord, Notion, β¦) β no longer produces a fabricated "Invalid value for 'operation'" error. The genuine missing *required* field (e.g. a channel) is still flagged.
+- **The Webhook β Respond-to-Webhook pattern** β needs no `onError`; n8n auto-returns a 500 if a node fails before the Respond node.
+- **IF / Filter / Switch v1 legacy shapes** β the native `conditions.{string|number|boolean}` shape validates correctly; `combinator` and `conditions.options` are optional (n8n defaults them); unary operators don't need `singleValue`.
+- **Optional chaining, string-keyed bracket access** (`$json['some-prop']`), fields named `test`/`null`/`undefined`, `this.helpers` usage, regex `$` anchors, and bare-object returns in `runOnceForAllItems` mode.
-**What to do**: Ignore - credentials are validated when workflow runs
+One precise caveat on template literals: they only evaluate **inside `{{ }}`**. A bare backtick string written as a plain field value (no `{{ }}`) is literal text β n8n evaluates only `{{ }}`, everything else is passed through verbatim.
---
## Summary
### Always Fix
-- β Security warnings
+- β Security warnings (surface under every profile)
- β Hardcoded credentials
- β SQL injection risks
-- β Production workflow errors
+- β Any error (`valid: false`) β these block activation
### Usually Fix
- β οΈ Error handling (production)
@@ -708,12 +668,11 @@ When accepting a warning, document why:
- β Rate limiting (low volume)
- β Query limits (small datasets)
-### Always Acceptable
-- β Known n8n issues (#304, #306, #338)
-- β Auto-sanitization warnings
-- β Metadata completeness (auto-fixed)
+### Not a fix at all β advice, not defects
+- β Best-practice advisories under `ai-friendly` / `strict` that don't apply to your workflow (weigh them per-case using this document)
+- β Outdated-`typeVersion` suggestions when your version is older-but-supported (that is normal n8n behavior)
-**Golden Rule**: If you accept a warning, document WHY.
+**Golden Rule**: If you accept an advisory, document WHY.
**Related Files**:
- **[SKILL.md](SKILL.md)** - Main validation guide
diff --git a/skills/n8n-validation-expert/README.md b/skills/n8n-validation-expert/README.md
index 950e900a..14b5dcce 100644
--- a/skills/n8n-validation-expert/README.md
+++ b/skills/n8n-validation-expert/README.md
@@ -32,11 +32,11 @@ Validation errors are common:
- Average 2-3 iterations to success
- 23 seconds thinking + 58 seconds fixing per cycle
-3. **Validation Profiles**
- - `minimal` - Quick checks, most permissive
- - `runtime` - Recommended for most use cases
- - `ai-friendly` - Reduces false positives for AI workflows
- - `strict` - Maximum safety, many warnings
+3. **Validation Profiles** (cumulative β each adds to the one below)
+ - `minimal` - Errors only; quick structural checks
+ - `runtime` - Errors + security/deprecation warnings; recommended default
+ - `ai-friendly` - Adds best-practice advisories (error-handling, rate-limit, outdated-typeVersion)
+ - `strict` - Adds leftover-property checks; maximum lint
4. **Auto-Sanitization System**
- Automatically fixes operator structure issues
@@ -45,10 +45,10 @@ Validation errors are common:
- Adds IF/Switch metadata
5. **False Positives**
- - Not all warnings need fixing
- - 40% of warnings are acceptable in context
- - Use `ai-friendly` profile to reduce by 60%
- - Document accepted warnings
+ - The classic false positives were fixed at the source (n8n-mcp β₯ 2.63.0) β nothing to ignore
+ - Remaining warnings are best-practice advisories (`ai-friendly` / `strict`) or security/deprecation notices (every profile)
+ - Not every advisory needs fixing β weigh it against your use case
+ - Document accepted advisories
## File Structure
@@ -80,13 +80,13 @@ n8n-validation-expert/
β
βββ FALSE_POSITIVES.md
β When warnings are acceptable
-β - Philosophy of warning acceptance
-β - 6 common false positive types
+β - Philosophy of advisory acceptance
+β - 6 common context-dependent advisories
β - When acceptable vs when to fix
β - Validation profile strategies
β - Decision framework
β - Documentation template
-β - Known n8n issues (#304, #306, #338)
+β - What the validator no longer flags (β₯ 2.63.0)
β
βββ README.md (this file)
Skill metadata and statistics
@@ -103,23 +103,23 @@ n8n-validation-expert/
| type_mismatch | Medium | β | Error |
| invalid_expression | Medium | β | Error |
| invalid_reference | Low | β | Error |
-| operator_structure | Low | β | Warning |
+| operator_structure | Low | β (normalized on save) | Not flagged (β₯ 2.63.0) |
## Key Insights
### 1. Validation is Iterative
Don't expect to get it right on the first try. Multiple validation cycles (typically 2-3) are normal and expected!
-### 2. False Positives Exist
-Many validation warnings are acceptable in production workflows. This skill helps you recognize which ones to address vs. which to ignore.
+### 2. Advisories vs. Errors
+The classic false positives are fixed at the source (n8n-mcp β₯ 2.63.0). Warnings you now see are either security/deprecation notices (act on them) or best-practice advisories (weigh per-case). This skill helps you tell them apart.
### 3. Auto-Sanitization Works
-Certain error types (like operator structure issues) are automatically fixed by n8n. Don't waste time manually fixing these!
+Operator structures (binary/unary `singleValue`, IF/Switch metadata) are normalized on save, and validation no longer errors on the un-normalized shape. Don't waste time hand-fixing these!
### 4. Profile Matters
-- `ai-friendly` reduces false positives by 60%
-- `runtime` is the sweet spot for most use cases
-- `strict` has value pre-production but is noisy
+- Profiles are cumulative: `minimal` β `runtime` β `ai-friendly` β `strict`
+- `runtime` is the everyday default (errors + security/deprecation)
+- `ai-friendly` / `strict` add best-practice advisories for pre-deploy review
### 5. Error Messages Help
Validation errors include fix guidance - read them carefully!
@@ -273,7 +273,7 @@ if (preview.fixCount > 0) {
- **n8n-mcp MCP Server**: Provides validation tools
- **n8n Validation API**: validate_node, validate_workflow, n8n_autofix_workflow
-- **n8n Issues**: #304 (IF metadata), #306 (Switch branches), #338 (credentials)
+- **Validator overhaul (n8n-mcp 2.63.0)**: fixed the false-positive classes this guide used to warn about
## Version History
diff --git a/skills/n8n-validation-expert/REVIEW_CHECKLIST.md b/skills/n8n-validation-expert/REVIEW_CHECKLIST.md
index 30eb3f58..cc237124 100644
--- a/skills/n8n-validation-expert/REVIEW_CHECKLIST.md
+++ b/skills/n8n-validation-expert/REVIEW_CHECKLIST.md
@@ -52,7 +52,7 @@ Pull the workflow first, then walk the list top to bottom. For each item, inspec
### Connection bugs (valid but broken)
- [ ] **Merge with 3+ sources but `numberOfInputs` still 2.** Third source silently drops. β **n8n-node-configuration** β NODE_FAMILY_GOTCHAS.md (Merge)
- [ ] **Merge index off-by-one.** `parameters.useDataOfInput` is 1-indexed; the wire sits at `connections..main[N-1]`. Mismatch passes through the wrong source silently. Verify with `n8n_get_workflow`. β **n8n-node-configuration** β NODE_FAMILY_GOTCHAS.md (Merge)
-- [ ] **Error output enabled but unwired, or wired but not enabled.** If `parameters.onError` is `continueErrorOutput` but `connections..main[1]` is empty, the error path goes nowhere; if a node feeds an error branch but `onError` isn't set, the branch is unreachable and a failure halts the workflow. *(Dedicated error-handling guidance is in flight; for now treat this as a connection/config audit.)* β **n8n-validation-expert**
+- [ ] **Error output enabled but unwired, or wired but not enabled.** If `parameters.onError` is `continueErrorOutput` but the node's error output is empty, failed items are silently dropped; if a node feeds an error branch but `onError` isn't set, the branch is unreachable and a failure halts the workflow. `validate_workflow` now surfaces both as warnings (n8n-mcp β₯ 2.63.0), but neither flips `valid:false`, so this stays a review item. **Locate the error output by node type**: it is the *last* `main[]` slot after the node's natural outputs β `main[1]` on a single-output node like HTTP Request, but `main[2]` on an IF (whose `main[1]` is the normal false branch) and similarly on Switch/Split In Batches. Don't mistake a wired second branch on a multi-output node for an unwired error output. β **n8n-validation-expert**
### Switch
- [ ] **No fallback output.** Without `parameters.options.fallbackOutput: "extra"`, unmatched items drop silently. β **n8n-node-configuration** β NODE_FAMILY_GOTCHAS.md (Switch)
@@ -99,7 +99,7 @@ Pull the workflow first, then walk the list top to bottom. For each item, inspec
- [ ] **`responseMode` left at `onReceived` for a request/response API.** Caller never sees the computed result; use `responseNode`. β **n8n-node-configuration** β NODE_FAMILY_GOTCHAS.md (Webhook)
- [ ] **Generic 500 for every failure.** Map status codes: 400 validation, 401/403 auth, 409 conflict, 429 rate limit. β **n8n-node-configuration**
- [ ] **`respondWith: "json"` body built with `JSON.stringify(...)`.** Double-encodes. Pass the object literal in expression mode. β **n8n-node-configuration** β NODE_FAMILY_GOTCHAS.md (Webhook)
-- [ ] **Fallible nodes (HTTP/DB/API) on a webhook path with no error branch.** A failure halts the workflow and the caller gets n8n's generic error. *(Until a dedicated error-handling skill lands, wire `onError: "continueErrorOutput"` + a 5xx Respond, and validate the wiring.)* β **n8n-workflow-patterns** (webhook), **n8n-validation-expert**
+- [ ] **Fallible nodes (HTTP/DB/API) on a webhook path with no error branch.** A failure halts the workflow and the caller gets n8n's generic error. Wire `onError: "continueErrorOutput"` + a 5xx Respond, and validate the wiring. β **n8n-error-handling**, **n8n-workflow-patterns** (webhook)
### HTTP Request
- [ ] **Auth header typed into `headerParameters` instead of a credential.** Use Bearer Auth / Header Auth credentials. β **n8n-node-configuration**, **n8n-mcp-tools-expert**
diff --git a/skills/n8n-validation-expert/SKILL.md b/skills/n8n-validation-expert/SKILL.md
index 43141562..6dda24d0 100644
--- a/skills/n8n-validation-expert/SKILL.md
+++ b/skills/n8n-validation-expert/SKILL.md
@@ -48,17 +48,17 @@ Validation is typically iterative:
**Doesn't block execution** - Workflow can be activated but may have issues
**Types**:
-- `best_practice` - Recommended but not required
-- `deprecated` - Using old API/feature
-- `performance` - Potential performance issue
+- `best_practice` - Recommended but not required β surfaces under `ai-friendly` / `strict` only
+- `deprecated` - Using old API/feature β surfaces under every profile
+- `security` - Hardcoded secrets, unauthenticated webhooks β surfaces under every profile
+- `performance` - Potential performance issue β advisory, `ai-friendly` / `strict`
-**Example**:
+**Example** (best-practice β appears under `ai-friendly` / `strict`):
```json
{
- "type": "best_practice",
- "property": "errorHandling",
- "message": "Slack API can have rate limits",
- "suggestion": "Add onError: 'continueRegularOutput' with retryOnFail"
+ "type": "warning",
+ "nodeName": "Slack",
+ "message": "Slack API can have rate limits and transient failures"
}
```
@@ -136,54 +136,35 @@ const result3 = validate_node({
## Validation Profiles
-Choose the right profile for your stage:
+The four profiles are **cumulative** (n8n-mcp β₯ 2.63.0): each surfaces everything the lower one does, plus more. The dividing line is best-practice *advisories* β `minimal` and `runtime` withhold them; `ai-friendly` and `strict` add them. Errors are the same across every profile except that `minimal` skips a few config-level checks (e.g. enum validation of an explicit `operation`). Security and deprecation warnings surface under every profile.
### minimal
-**Use when**: Quick checks during editing
-
-**Validates**:
-- Only required fields
-- Basic structure
+**Use when**: Quick structural checks while wiring a workflow together.
-**Pros**: Fastest, most permissive
-**Cons**: May miss issues
+**Surfaces**: hard errors that would stop execution (missing required fields, empty code, broken connections). Skips enum checks and all advisories.
-### runtime (RECOMMENDED)
-**Use when**: Pre-deployment validation
+**Fastest and most permissive.**
-**Validates**:
-- Required fields
-- Value types
-- Allowed values
-- Basic dependencies
+### runtime (RECOMMENDED default)
+**Use when**: Ongoing validation as you build; the everyday profile.
-**Pros**: Balanced, catches real errors
-**Cons**: Some edge cases missed
+**Surfaces**: errors (required fields, value types, allowed values, dependencies, broken references) plus security and deprecation warnings. **No** best-practice advisories.
-**This is the recommended profile for most use cases**
+**Balanced β catches everything that breaks, stays quiet about style.**
### ai-friendly
-**Use when**: AI-generated configurations
+**Use when**: You want the best-practice advice before deploying.
-**Validates**:
-- Same as runtime
-- Reduces false positives
-- More tolerant of minor issues
+**Surfaces**: everything `runtime` does, **plus** best-practice advisories β per-node "without error handling" suggestions, "webhook should always send a response", rate-limit notes, outdated-`typeVersion` suggestions, `cachedResultName` and long-chain hints.
-**Pros**: Less noisy for AI workflows
-**Cons**: May allow some questionable configs
+**Note**: `ai-friendly` is *stricter* than `runtime`, not looser. (Older docs described it as reducing false positives β that was true only while profile gating was broken; it is fixed now.)
### strict
-**Use when**: Production deployment, critical workflows
+**Use when**: Hardening a production-critical workflow.
-**Validates**:
-- Everything
-- Best practices
-- Performance concerns
-- Security issues
+**Surfaces**: everything `ai-friendly` does, **plus** leftover-property checks ("property 'X' won't be used β not visible with current settings").
-**Pros**: Maximum safety
-**Cons**: Many warnings, some false positives
+**Maximum lint.** With the false positives fixed at the source, its warnings are advice to weigh, not noise to fight.
---
@@ -205,14 +186,16 @@ Every type above has worked examples (broken config β fix) plus the patchNodeF
## Auto-Sanitization System
-**Automatically fixes common operator structure issues** on ANY workflow update β `n8n_create_workflow`, `n8n_update_partial_workflow`, or any save. Trust it; don't hand-fix these.
+**Automatically normalizes common operator structures** on ANY workflow update β `n8n_create_workflow`, `n8n_update_partial_workflow`, or any save. Trust it; don't hand-fix these.
-**What it fixes**:
-- **Binary operators** (equals, notEquals, contains, notContains, greaterThan, lessThan, startsWith, endsWith) β removes the wrong `singleValue` property.
+**What it normalizes on save**:
+- **Binary operators** (equals, notEquals, contains, notContains, greaterThan, lessThan, startsWith, endsWith) β removes a stray `singleValue` property.
- **Unary operators** (isEmpty, isNotEmpty, true, false) β adds `singleValue: true`.
-- **IF/Switch metadata** β adds complete `conditions.options` metadata for IF v2.2+ and Switch v3.2+.
+- **IF/Switch metadata** β fills in `conditions.options` for IF v2.2+ and Switch v3.2+.
-**What it CANNOT fix** (handle manually): broken connections to non-existent nodes (use `cleanStaleConnections`), branch-count mismatches (add/remove connections or rules), and paradoxical corrupt states (may need manual DB intervention).
+**Validation no longer errors on these shapes** (n8n-mcp β₯ 2.63.0). n8n derives unary-ness from the operator name and defaults the `conditions.options` sub-fields, so `validate_node` / `validate_workflow` accept a condition whether or not `singleValue` and the options metadata are present β the sanitizer just tidies the canonical form on save. (Older servers wrongly errored on the un-normalized shape; if you see that, upgrade.) What still *is* a real error: a v1-shaped `conditions` object on a v2 node, an empty filter with no conditions, and legacy v1 operator names (e.g. `smaller`) inside a v2 structure.
+
+**What the sanitizer CANNOT fix** (handle manually): broken connections to non-existent nodes (use `cleanStaleConnections`), branch-count mismatches (add/remove connections or rules), and paradoxical corrupt states (may need manual DB intervention).
Before/after examples and the full cannot-fix detail are in **[ERROR_CATALOG.md](ERROR_CATALOG.md)** (Auto-Sanitization sections).
@@ -220,16 +203,18 @@ Before/after examples and the full cannot-fix detail are in **[ERROR_CATALOG.md]
## False Positives
-Validation warnings that are technically "wrong" but acceptable in your use case. Not every warning needs a fix β many are context-dependent. Common ones and when each is acceptable vs. worth fixing:
+The validator overhaul (n8n-mcp β₯ 2.63.0) removed the classic false positives β template literals inside expressions, optional chaining, omitted-operation defaults, the Webhook β Respond-to-Webhook pattern, IF/Filter legacy shapes, and more no longer fire. There is no standing list of "known false positives to ignore."
+
+What remains are **best-practice advisories** (surfaced only under `ai-friendly` / `strict`) that flag a real trade-off but may be acceptable in your case. Not every advisory needs a fix β many are context-dependent. Common ones and when each is acceptable vs. worth fixing:
-- **"Missing error handling"** β OK for dev/testing and non-critical notifications; fix for production handling important data.
+- **"...without error handling"** β OK for dev/testing and non-critical notifications; fix for production handling important data. (Never a hard error β style doesn't block execution.)
- **"No retry logic"** β OK for idempotent ops, APIs with their own retry, manual triggers; fix for flaky external services and production automation.
-- **"Missing rate limiting"** β OK for internal/low-volume/server-side-limited APIs; fix for public, high-volume APIs.
+- **"...rate limits and transient failures"** β OK for internal/low-volume/server-side-limited APIs; fix for public, high-volume APIs.
- **"Unbounded query"** β OK for small known datasets, aggregations, dev/testing; fix for production queries on large tables.
-**Reduce false positives** with the `ai-friendly` profile (e.g. `validate_node({nodeType, config, profile: "ai-friendly"})`).
+Security and deprecation warnings, by contrast, surface under *every* profile and should be treated as real.
-Full per-case guidance, security/credential warnings, known n8n false-positive issues (#304, #306, #338), profile strategies, the "should I fix this?" decision framework, and how to document accepted warnings are in **[FALSE_POSITIVES.md](FALSE_POSITIVES.md)**.
+Full per-case guidance, the list of what the validator no longer flags, profile strategies, the "should I fix this?" decision framework, and how to document accepted advisories are in **[FALSE_POSITIVES.md](FALSE_POSITIVES.md)**.
---
@@ -317,14 +302,14 @@ validate_workflow({
**Fix**: Remove stale connection or create missing node
-#### 2. Circular Dependencies
+#### 2. Cycles (warning, not an error)
```json
{
- "error": "Circular dependency detected: Node A β Node B β Node A"
+ "warning": "Workflow contains a cycle: Node A β Node B β Node A"
}
```
-**Fix**: Restructure workflow to remove loop
+A cycle is a **warning**, not a hard error (n8n-mcp β₯ 2.63.0) β runtime-controlled loops (error-retry, data-driven pagination, a router feeding back) execute to completion and are legitimate. **Fix** only if the loop is unintentional: ensure the cycle has a real exit (a conditional node, an error output, or a bounded counter) so it can't spin forever.
#### 3. Multiple Start Nodes
```json
@@ -485,9 +470,9 @@ For comprehensive error catalogs, false positives, and workflow review:
**Key Points**:
1. **Validation is iterative** (avg 2-3 cycles, 23s + 58s)
2. **Errors must be fixed**, warnings are optional
-3. **Auto-sanitization** fixes operator structures automatically
-4. **Use runtime profile** for balanced validation
-5. **False positives exist** - learn to recognize them
+3. **Auto-sanitization** normalizes operator structures on save; validation no longer errors on the raw shape
+4. **Use runtime profile** by default; step up to `ai-friendly`/`strict` for best-practice advisories
+5. **Classic false positives are fixed** (β₯ 2.63.0) β remaining warnings are advisories or security/deprecation notices, not validator mistakes
6. **Read error messages** - they contain fix guidance
**Validation Process**:
diff --git a/skills/using-n8n-mcp-skills/SKILL.md b/skills/using-n8n-mcp-skills/SKILL.md
index 3a632e38..852a6541 100644
--- a/skills/using-n8n-mcp-skills/SKILL.md
+++ b/skills/using-n8n-mcp-skills/SKILL.md
@@ -71,7 +71,7 @@ If you catch yourself thinking any of these, stop and invoke the named skill fir
| "Date math β I'll drop in a DateTime node" | `n8n-expression-syntax` β Luxon inline is almost always right |
| "I'll wire a Merge with 3 sources" | `n8n-node-configuration` β Merge defaults to 2 inputs; the 3rd silently drops |
| "Validation passed, I'm ready to activate" | `n8n-validation-expert` + `n8n-workflow-patterns` β run the antipattern scan |
-| "Validation threw an error I don't understand" | `n8n-validation-expert` β some warnings are false positives, some errors are real |
+| "Validation threw an error I don't understand" | `n8n-validation-expert` β what each error and warning means, and which are must-fix vs. best-practice advice |
| "I'll reference `$json.x` here" | `n8n-expression-syntax` β prefer `$('Node').item.json.x` in branchy workflows |
| "This webhook/scheduled flow is happy-path only" | `n8n-error-handling` β wire an error branch on every fallible node; 4xx caller faults, 5xx yours |
| "I'll pass this file/image through as JSON" | `n8n-binary-and-data` β file contents live in `$binary`, and can't cross the agent-tool boundary |
@@ -116,7 +116,7 @@ closes the gap where a tool's full description isn't loaded until first use.
- `n8n_update_partial_workflow` β incremental diff ops (`{id, operations:[β¦]}`): addNode, updateNode, patchNodeField, addConnection, activateWorkflow, etc. Preferred for edits.
- `n8n_update_full_workflow` β full replacement.
- `n8n_autofix_workflow` β auto-fix common issues.
-- `n8n_generate_workflow` / `n8n_deploy_template` β generate or deploy a template to the instance.
+- `n8n_deploy_template` β deploy a template to the instance.
**Validate** (necessary, not sufficient β always pair with the antipattern scan)
- `validate_workflow` β full JSON in, errors/warnings/fixes out. Node types here are **LONG form** (`n8n-nodes-base.set`).