N8n Validation Expert
Romuald CzlonkowskiCommunityCreated by Romuald Czlonkowski
- Skill
- Tool integration
- Automation and integrations
- MIT
- No issues found
AutoGineer summary
Written by AutoGineer from the skill’s own files, not by the author
The n8n-validation-expert skill provides a structured approach to managing workflow validation. It emphasizes an iterative validation loop, where users perform multiple cycles of validation and correction to achieve a stable workflow. The skill covers error severity levels, explains the four validation profiles (minimal, runtime, ai-friendly, strict), and details how to handle common error types, auto-sanitization, and false positives. It also provides strategies for testing workflows and performing comprehensive audits on existing configurations.
What it does
- Interprets and explains validation errors and warnings returned by n8n.
- Provides guidance on fixing common issues like missing fields, type mismatches, and broken references.
- Explains how to use different validation profiles to balance strictness and advisory feedback.
- Details the auto-sanitization process for operator structures.
- Offers strategies for testing workflows, including handling triggers and pinned data.
- Provides a framework for auditing existing workflows for security and best practices.
When to use it
- When a validate_node or validate_workflow call returns errors or warnings.
- When encountering unexpected validation results or potential false positives.
- When hardening a production-critical workflow using strict validation profiles.
- When troubleshooting workflow execution failures related to configuration.
- When reviewing an existing workflow for potential improvements or security risks.
Advantages
- Reduces time spent debugging by providing clear, actionable fix guidance.
- Helps distinguish between critical errors that block execution and optional best-practice warnings.
- Standardizes the validation process through an iterative, proven loop.
- Clarifies the use of validation profiles to suit different development stages.
- Provides specific strategies for handling complex scenarios like cycles and disconnected nodes.
Usage examples
- “I am getting a 'missing_required' error on my Slack node. How do I fix it?”
- The agent will explain that a required field is missing, suggest using get_node to identify the specific field, and guide you through providing the necessary value.
- “My workflow validates but I am getting a warning about 'best_practice'. Should I fix it?”
- The agent will explain that best-practice warnings are optional, help you evaluate the trade-offs based on your specific use case, and advise whether to address it or accept it.
- “How do I use the strict profile to harden my production workflow?”
- The agent will explain how to configure the validation call to use the 'strict' profile and what additional checks, such as leftover-property detection, will be performed.
- “I have a workflow that validates but fails at runtime. What should I do?”
- The agent will guide you through testing strategies, such as using n8n_test_workflow with pinned data or direct execution, to isolate the issue.
Install
Download the skill as a ZIP:
https://autogineer.com/agent-skills/czlonkowski/n8n-validation-expert/downloadUnzip it into the cross-client skills folder of your project:
mkdir -p .agents/skills && unzip n8n-validation-expert.zip -d .agents/skills
Documentation by Romuald Czlonkowski
n8n Validation Expert
Expert guide for interpreting and fixing n8n validation errors.
Validation Philosophy
Validate early, validate often
Validation is typically iterative:
Expect validation feedback loops
Usually 2-3 validate → fix cycles
Average: 23s thinking about errors, 58s fixing them
Key insight: Validation is an iterative process, not one-shot!
Error Severity Levels
1. Errors (Must Fix)
Blocks workflow execution - Must be resolved before activation
Types:
missing_required- Required field not providedinvalid_value- Value doesn't match allowed optionstype_mismatch- Wrong data type (string instead of number)invalid_reference- Referenced node doesn't existinvalid_expression- Expression syntax error
Example:
{
"type": "missing_required",
"property": "channel",
"message": "Channel name is required",
"fix": "Provide a channel name (lowercase, no spaces, 1-80 characters)"
}2. Warnings (Should Fix)
Doesn't block execution - Workflow can be activated but may have issues
Types:
best_practice- Recommended but not required — surfaces underai-friendly/strictonlydeprecated- Using old API/feature — surfaces under every profilesecurity- Hardcoded secrets, unauthenticated webhooks — surfaces under every profileperformance- Potential performance issue — advisory,ai-friendly/strict
Example (best-practice — appears under ai-friendly / strict):
{
"type": "warning",
"nodeName": "Slack",
"message": "Slack API can have rate limits and transient failures"
}3. Suggestions (Optional)
Nice to have - Improvements that could enhance workflow
Types:
optimization- Could be more efficientalternative- Better way to achieve same result
The Validation Loop
Pattern from Telemetry
7,841 occurrences of this pattern:
1. Configure node
↓
2. validate_node (23 seconds thinking about errors)
↓
3. Read error messages carefully
↓
4. Fix errors
↓
5. validate_node again (58 seconds fixing)
↓
6. Repeat until valid (usually 2-3 iterations)Example
// Iteration 1
let config = {
resource: "channel",
operation: "create"
};
const result1 = validate_node({
nodeType: "nodes-base.slack",
config,
profile: "runtime"
});
// → Error: Missing "name"
// ⏱️ 23 seconds thinking...
// Iteration 2
config.name = "general";
const result2 = validate_node({
nodeType: "nodes-base.slack",
config,
profile: "runtime"
});
// → Error: Missing "text"
// ⏱️ 58 seconds fixing...
// Iteration 3
config.text = "Hello!";
const result3 = validate_node({
nodeType: "nodes-base.slack",
config,
profile: "runtime"
});
// → Valid! ✅This is normal! Don't be discouraged by multiple iterations.
Validation Profiles
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 structural checks while wiring a workflow together.
Surfaces: hard errors that would stop execution (missing required fields, empty code, broken connections). Skips enum checks and all advisories.
Fastest and most permissive.
runtime (RECOMMENDED default)
Use when: Ongoing validation as you build; the everyday profile.
Surfaces: errors (required fields, value types, allowed values, dependencies, broken references) plus security and deprecation warnings. No best-practice advisories.
Balanced — catches everything that breaks, stays quiet about style.
ai-friendly
Use when: You want the best-practice advice before deploying.
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.
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: Hardening a production-critical workflow.
Surfaces: everything ai-friendly does, plus leftover-property checks ("property 'X' won't be used — not visible with current settings").
Maximum lint. With the false positives fixed at the source, its warnings are advice to weigh, not noise to fight.
Common Error Types
Five core error types, in rough order of frequency:
missing_required— a required field isn't provided. Useget_nodeto see required fields, then add it.invalid_value— value doesn't match allowed options (enums are case-sensitive). Check the error's allowed list orget_node.type_mismatch— wrong data type (string"100"vs number100). Convert to the expected type.invalid_expression— expression syntax error (missing{{}}, typos). See the n8n Expression Syntax skill.invalid_reference— referenced node doesn't exist (renamed, deleted, or misspelled). Fix the name orcleanStaleConnections.
A sixth class, patchNodeField errors (find-not-found, ambiguous match, invalid/unsafe regex), surfaces when a patchNodeField op fails during n8n_update_partial_workflow — it's strict by design and errors rather than silently continuing.
Every type above has worked examples (broken config → fix) plus the patchNodeField error cases and their fixes in ERROR_CATALOG.md.
Auto-Sanitization System
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 normalizes on save:
Binary operators (equals, notEquals, contains, notContains, greaterThan, lessThan, startsWith, endsWith) — removes a stray
singleValueproperty.Unary operators (isEmpty, isNotEmpty, true, false) — adds
singleValue: true.IF/Switch metadata — fills in
conditions.optionsfor IF v2.2+ and Switch v3.2+.
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 (Auto-Sanitization sections).
False Positives
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.
Known exceptions (n8n-mcp 2.85.0, reported upstream; re-check after upgrading):
ERROR "Incorrect error output configuration… appear to be error handlers but are in main[0]" on a fan-out where one target is a Respond to Webhook or Send Email node, or has error / fail / catch / exception in its name. Moving Respond to Webhook onto
main[1], as suggested, means the webhook only answers when the upstream node fails. Treat it as a false positive only when the message matches this text exactly and you've inspectedconnectionsand confirmed the named node sits on the success path by design. In that case keep the wiring, say in your reply that you're ignoring n8n-mcp#1111 and why, and don't runn8n_autofix_workflowwith the default fix types (excludeerror-output-config, or it may rewire the success path). Every othervalid: falseerror still gets fixed. (n8n-mcp#1111)Warning "Possible missing $ prefix" on
json/itemsinside a string, e.g.$jmespath($('X').all(), "[?json.country=='PL'].json.name"). Thejson.prefix is required there, so ignore the warning. (#1115)validate_nodeon alanguage: "pythonNative"Code node → "Code cannot be empty" (jsCode). The error is false; validate the workflow instead. (#1112)Python "Return value must be a list of dicts" for a single-dict return in all-items mode. n8n accepts it and emits one item. (#1113)
Blind spots (valid workflow, wrong result at runtime): $jmespath syntax/quoting mistakes inside expressions (#1114); any JS error inside {{ }}, which resolves to null while the execution stays green (see n8n-expression-syntax); native-Python mistakes such as legacy _input/_json, dot access, blocked imports and classes (#1113, see n8n-code-python). Validation plus a successful run still isn't proof: inspect the output values.
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:
"...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.
"...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.
Security and deprecation warnings, by contrast, surface under every profile and should be treated as real.
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.
Validation Result Structure
Complete Response
{
"valid": false,
"errors": [
{
"type": "missing_required",
"property": "channel",
"message": "Channel name is required",
"fix": "Provide a channel name (lowercase, no spaces)"
}
],
"warnings": [
{
"type": "best_practice",
"property": "errorHandling",
"message": "Slack API can have rate limits",
"suggestion": "Add onError: 'continueRegularOutput'"
}
],
"suggestions": [
{
"type": "optimization",
"message": "Consider using batch operations for multiple messages"
}
],
"summary": {
"hasErrors": true,
"errorCount": 1,
"warningCount": 1,
"suggestionCount": 1
}
}How to Read It
Check
validfirst —truemeans the config is valid;falsemeans there are errors to fix before deployment.Fix
errorsfirst — each carries aproperty,message, andfix. These must be resolved.Review
warnings— each has amessageandsuggestion; decide per-case whether to address it (see False Positives above).Consider
suggestions— optional improvements, not required.
Workflow Validation
validate_workflow (Structure)
Validates entire workflow, not just individual nodes
Checks:
Node configurations - Each node valid
Connections - No broken references
Expressions - Syntax and references valid
Flow - Logical workflow structure
Example:
validate_workflow({
workflow: {
nodes: [...],
connections: {...}
},
options: {
validateNodes: true,
validateConnections: true,
validateExpressions: true,
profile: "runtime"
}
})Common Workflow Errors
1. Broken Connections
{
"error": "Connection from 'Transform' to 'NonExistent' - target node not found"
}Fix: Remove stale connection or create missing node
2. Cycles (warning, not an error)
{
"warning": "Workflow contains a cycle: Node A → Node B → Node A"
}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
{
"warning": "Multiple trigger nodes found - only one will execute"
}Fix: Remove extra triggers or split into separate workflows
4. Disconnected Nodes
{
"warning": "Node 'Transform' is not connected to workflow flow"
}Fix: Connect node or remove if unused
Recovery Strategies
Strategy 1: Start Fresh
When: Configuration is severely broken
Steps:
Note required fields from
get_nodeCreate minimal valid configuration
Add features incrementally
Validate after each addition
Strategy 2: Binary Search
When: Workflow validates but executes incorrectly
Steps:
Remove half the nodes
Validate and test
If works: problem is in removed nodes
If fails: problem is in remaining nodes
Repeat until problem isolated
Strategy 3: Clean Stale Connections
When: "Node not found" errors
Steps:
n8n_update_partial_workflow({
id: "workflow-id",
operations: [{
type: "cleanStaleConnections"
}]
})Strategy 4: Use Auto-fix
When: Validation errors that can be automatically resolved
Steps:
// Preview fixes (default - doesn't apply)
n8n_autofix_workflow({
id: "workflow-id",
applyFixes: false,
confidenceThreshold: "medium" // high, medium, low
})
// Review fixes, then apply
n8n_autofix_workflow({
id: "workflow-id",
applyFixes: true
})Auto-Fix Capabilities
The n8n_autofix_workflow tool can fix these issue types:
expression-format - Missing
=prefix in expressions (e.g.,{{ $json.field }}→={{ $json.field }})typeversion-correction - Downgrades nodes with unsupported typeVersions
error-output-config - Removes conflicting onError settings
node-type-correction - Fixes unknown node types using similarity matching (90%+ confidence)
webhook-missing-path - Generates UUIDs for webhook nodes missing path configuration
typeversion-upgrade - Smart upgrades to latest node versions with auto-migration
version-migration - Guidance for complex breaking changes requiring manual steps
Confidence levels: high (90%+, safe to auto-apply), medium (70-89%, review recommended), low (<70%, manual review required)
// Preview all fixes
n8n_autofix_workflow({id: "workflow-id"})
// Only apply high-confidence fixes
n8n_autofix_workflow({
id: "workflow-id",
applyFixes: true,
confidenceThreshold: "high"
})
// Target specific fix types
n8n_autofix_workflow({
id: "workflow-id",
fixTypes: ["expression-format", "typeversion-upgrade"],
applyFixes: true
})Post-update guidance: For version upgrades, check the postUpdateGuidance field in the response for step-by-step migration instructions.
Best Practices
✅ Do
Validate after every significant change
Read error messages completely
Fix errors iteratively (one at a time)
Use
runtimeprofile for pre-deploymentCheck
validfield before assuming successTrust auto-sanitization for operator issues
Use
get_nodewhen unclear about requirementsDocument false positives you accept
❌ Don't
Skip validation before activation
Try to fix all errors at once
Ignore error messages
Use
strictprofile during development (too noisy)Assume validation passed (always check result)
Manually fix auto-sanitization issues
Deploy with unresolved errors
Ignore all warnings (some are important!)
Running the workflow after it validates
validate_workflow checks structure, parameters and expressions — it never runs anything. A workflow that validates cleanly can still fail on real data, so run it once before calling it done.
With a webhook, form or chat trigger: n8n_test_workflow({workflowId}) — the default method: "auto" detects the trigger and fires it over HTTP (the workflow must be active).
Without such a trigger (Manual Trigger, Schedule, sub-workflow) there is no HTTP entry point. Use the pin-data path, which runs through n8n's own MCP server (N8N_MCP_ACCESS_TOKEN, n8n 2.34+):
n8n_test_workflow({workflowId, method: "prepare"})— lists the nodes that need pinned data.Build one sample item per listed node, keyed by node name, each item wrapped in
{json: {...}}:{"When clicking 'Test workflow'": [{"json": {"orderId": "1234", "email": "[email protected]"}}]}A flat object instead of an array of
{json}items is the usual mistake here.n8n_test_workflow({workflowId, method: "pinned", pinData})— runs it with that data and waits for the result.
For a quick manual run without pinned data, method: "direct" starts a manual execution and returns as soon as it has started; poll n8n_executions({action: "get", id: executionId, mode: "error"}) for the outcome. Nothing is pinned on a direct run, so every node executes and any external call it makes is real — and even pinned only pins trigger, credentialed and HTTP Request nodes. executionMode: "production" changes the execution context, not whether there are side effects; leave it at the default unless the user asked for a production run.
method: "auto" never runs a workflow through n8n's MCP server. On a workflow with no external trigger it reports that fact and names prepare/pinned/direct; the routed methods only run when you ask for them by name.
Consent before the first routed run. n8n refuses these calls for a workflow whose "Available in MCP" setting is off, which comes back as WORKFLOW_NOT_EXPOSED. Re-running with exposeToMcp: true turns that setting on and retries once. It is a visible, persistent setting on the workflow (and enabling it is a workflow update, so a concurrent UI edit can be overwritten) — ask the user before passing it. The consent flow only ever enables the setting; nothing disables it implicitly. Turning it off again is a deliberate act: n8n_update_partial_workflow({id: workflowId, operations: [{type: "updateSettings", settings: {availableInMCP: false}}]}), or the toggle in the n8n UI.
Reading the result: a run that started and then failed comes back as EXECUTION_FAILED with the executionId — inspect it with n8n_executions({action: "get", id, mode: "error"}) and fix from the node that threw, then validate and run again.
Reviewing an existing workflow
Validating as you build (the loop above) is for catching schema and shape errors in your own in-progress work. Reviewing an existing workflow — yours or one you've been handed — is a different job: the workflow already passes validate_workflow clean, and you're hunting for the issues validation doesn't see (silent connection bugs, injection-prone queries, dropped-item Switches, Set/Code antipatterns, missing error paths). For that, pull the workflow with n8n_get_workflow and walk REVIEW_CHECKLIST.md — a severity-tiered audit (MUST FIX / SHOULD FIX / NICE TO HAVE) where every item points to the canonical skill for the fix. Run n8n_audit_instance alongside it to surface hardcoded secrets and unauthenticated webhooks across the whole instance.
Detailed Guides
For comprehensive error catalogs, false positives, and workflow review:
ERROR_CATALOG.md - Complete list of error types with examples
FALSE_POSITIVES.md - When warnings are acceptable
REVIEW_CHECKLIST.md - Severity-tiered audit for reviewing an existing workflow
Summary
Key Points:
Validation is iterative (avg 2-3 cycles, 23s + 58s)
Errors must be fixed, warnings are optional
Auto-sanitization normalizes operator structures on save; validation no longer errors on the raw shape
Use runtime profile by default; step up to
ai-friendly/strictfor best-practice advisoriesClassic false positives are fixed (≥ 2.63.0) — remaining warnings are advisories or security/deprecation notices, not validator mistakes
Read error messages - they contain fix guidance
Validation Process:
Validate → Read errors → Fix → Validate again
Repeat until valid (usually 2-3 iterations)
Review warnings and decide if acceptable
Deploy with confidence
Related Skills & Tools:
n8n MCP Tools Expert - Use validation tools correctly
n8n Expression Syntax - Fix expression errors
n8n Node Configuration - Understand required fields
n8n_audit_instance- Proactive security validation (hardcoded secrets, unauthenticated webhooks, missing error handling, data retention)
Files
- ATTRIBUTION.mdAdded by AutoGineer899 B
- ERROR_CATALOG.md23.2 KiB
- FALSE_POSITIVES.md21.0 KiB
- LICENSELicence file added by AutoGineer1.1 KiB
- README.md8.3 KiB
- REVIEW_CHECKLIST.md16.2 KiB
- SKILL.md22.4 KiB
FALSE_POSITIVES.md
False Positives Guide
When validation warnings are acceptable and how to handle them.
What Are False Positives?
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 every warning needs a fix, but the reason has changed (n8n-mcp ≥ 2.63.0).
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. A few new ones have surfaced since; they're listed, with strict conditions, under Known false positives and blind spots.
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 (
minimalthroughstrict). Treat these as real.Best-practice advisories — error-handling suggestions, rate-limit notes, outdated-
typeVersionsuggestions,cachedResultNameadvice, long-chain hints. These surface only underai-friendlyandstrict.minimalandruntimenever 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.
Philosophy
✅ Good Practice
1. Validate with 'runtime' (errors + security/deprecation only)
2. Fix all ERRORS
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 security and deprecation warnings
2. Treat every 'strict' advisory as a mandatory fix (or as noise to blank-ignore)
3. Deploy without reading the errorsCommon False Positives
1. Missing Error Handling
Warning (surfaces under ai-friendly / strict only):
{
"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
// Testing workflow - failures are obvious
{
"name": "Test Slack Integration",
"nodes": [{
"type": "n8n-nodes-base.slack",
"parameters": {
"resource": "message",
"operation": "post",
"channel": "#test"
// No error handling - OK for testing
}
}]
}Reasoning: You WANT to see failures during testing.
✅ Non-Critical Notifications
// Nice-to-have notification
{
"name": "Optional Slack Notification",
"parameters": {
"channel": "#general",
"text": "FYI: Process completed"
// If this fails, no big deal
}
}Reasoning: Notification failure doesn't affect core functionality.
✅ Manual Trigger Workflows
// Manual workflow - user is watching
{
"nodes": [{
"type": "n8n-nodes-base.webhook",
"parameters": {
"path": "manual-test"
// No error handling - user will retry manually
}
}]
}Reasoning: User is present to see and handle errors.
When to Fix
❌ Production Automation
// BAD: Critical workflow without error handling
{
"name": "Process Customer Orders",
"nodes": [{
"type": "n8n-nodes-base.postgres",
"parameters": {
"query": "INSERT INTO orders..."
// ❌ Should have error handling!
}
}]
}Fix (modern onError, set at node level):
{
"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..."
}
}Prefer
onErrorover the legacycontinueOnFail: true— the validator flagscontinueOnFailas deprecated and n8n's UI no longer surfaces it cleanly. And note: if you setonError: '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
// BAD: Payment processing without error handling
{
"name": "Process Payment",
"type": "n8n-nodes-base.stripe"
// ❌ Payment failures MUST be handled!
}2. No Retry Logic
Warning:
{
"type": "best_practice",
"message": "External API calls should retry on failure",
"suggestion": "Add retryOnFail: true with exponential backoff"
}When Acceptable
✅ APIs with Built-in Retry
// Stripe has its own retry mechanism
{
"type": "n8n-nodes-base.stripe",
"parameters": {
"resource": "charge",
"operation": "create"
// Stripe SDK retries automatically
}
}✅ Idempotent Operations
// GET request - safe to retry manually if needed
{
"method": "GET",
"url": "https://api.example.com/status"
// Read-only, no side effects
}✅ Local/Internal Services
// Internal API with high reliability
{
"url": "http://localhost:3000/process"
// Local service, failures are rare and obvious
}When to Fix
❌ Flaky External APIs
// BAD: Known unreliable API without retries
{
"url": "https://unreliable-api.com/data"
// ❌ Should retry!
}
// GOOD:
{
"url": "https://unreliable-api.com/data",
"retryOnFail": true,
"maxTries": 3,
"waitBetweenTries": 2000
}❌ Non-Idempotent Operations
// BAD: POST without retry - may lose data
{
"method": "POST",
"url": "https://api.example.com/create"
// ❌ Could timeout and lose data
}3. Missing Rate Limiting
Warning:
{
"type": "best_practice",
"message": "API may have rate limits",
"suggestion": "Add rate limiting or batch requests"
}When Acceptable
✅ Internal APIs
// Internal microservice - no rate limits
{
"url": "http://internal-api/process"
// Company controls both ends
}✅ Low-Volume Workflows
// Runs once per day
{
"trigger": {
"type": "n8n-nodes-base.cron",
"parameters": {
"mode": "everyDay",
"hour": 9
}
},
"nodes": [{
"type": "n8n-nodes-base.httpRequest",
"parameters": {
"url": "https://api.example.com/daily-report"
// Once per day = no rate limit concerns
}
}]
}✅ APIs with Server-Side Limits
// API returns 429 and n8n handles it
{
"url": "https://api.example.com/data",
"options": {
"response": {
"response": {
"neverError": false // Will error on 429
}
}
},
"retryOnFail": true // Retry on 429
}When to Fix
❌ High-Volume Public APIs
// BAD: Loop hitting rate-limited API
{
"nodes": [{
"type": "n8n-nodes-base.splitInBatches",
"parameters": {
"batchSize": 100
}
}, {
"type": "n8n-nodes-base.httpRequest",
"parameters": {
"url": "https://api.github.com/..."
// ❌ GitHub has strict rate limits!
}
}]
}
// GOOD: Add rate limiting
{
"type": "n8n-nodes-base.httpRequest",
"parameters": {
"url": "https://api.github.com/...",
"options": {
"batching": {
"batch": {
"batchSize": 10,
"batchInterval": 1000 // 1 second between batches
}
}
}
}
}4. Unbounded Database Queries
Warning:
{
"type": "performance",
"message": "SELECT without LIMIT can return massive datasets",
"suggestion": "Add LIMIT clause or use pagination"
}When Acceptable
✅ Small Known Datasets
// Config table with ~10 rows
{
"query": "SELECT * FROM app_config"
// Known to be small, no LIMIT needed
}✅ Aggregation Queries
// COUNT/SUM operations
{
"query": "SELECT COUNT(*) as total FROM users WHERE active = true"
// Aggregation, not returning rows
}✅ Development/Testing
// Testing with small dataset
{
"query": "SELECT * FROM test_users"
// Test database has 5 rows
}When to Fix
❌ Production Queries on Large Tables
// BAD: User table could have millions of rows
{
"query": "SELECT * FROM users"
// ❌ Could return millions of rows!
}
// GOOD: Add LIMIT
{
"query": "SELECT * FROM users LIMIT 1000"
}
// BETTER: Use pagination
{
"query": "SELECT * FROM users WHERE id > {{$json.lastId}} LIMIT 1000"
}5. Missing Input Validation
Warning:
{
"type": "best_practice",
"message": "Webhook doesn't validate input data",
"suggestion": "Add IF node to validate required fields"
}When Acceptable
✅ Internal Webhooks
// Webhook from your own backend
{
"type": "n8n-nodes-base.webhook",
"parameters": {
"path": "internal-trigger"
// Your backend already validates
}
}✅ Trusted Sources
// Webhook from Stripe (cryptographically signed)
{
"type": "n8n-nodes-base.webhook",
"parameters": {
"path": "stripe-webhook",
"authentication": "headerAuth"
// Stripe signature validates authenticity
}
}When to Fix
❌ Public Webhooks
// BAD: Public webhook without validation
{
"type": "n8n-nodes-base.webhook",
"parameters": {
"path": "public-form-submit"
// ❌ Anyone can send anything!
}
}
// GOOD: Add validation
{
"nodes": [
{
"name": "Webhook",
"type": "n8n-nodes-base.webhook"
},
{
"name": "Validate Input",
"type": "n8n-nodes-base.if",
"parameters": {
"conditions": {
"boolean": [
{
"value1": "={{$json.body.email}}",
"operation": "isNotEmpty"
},
{
"value1": "={{$json.body.email}}",
"operation": "regex",
"value2": "^[^@]+@[^@]+\\.[^@]+$"
}
]
}
}
}
]
}6. Hardcoded Credentials
Warning:
{
"type": "security",
"message": "Credentials should not be hardcoded",
"suggestion": "Use n8n credential system"
}When Acceptable
✅ Public APIs (No Auth)
// Truly public API with no secrets
{
"url": "https://api.ipify.org"
// No credentials needed
}✅ Demo/Example Workflows
// Example workflow in documentation
{
"url": "https://example.com/api",
"headers": {
"Authorization": "Bearer DEMO_TOKEN"
}
// Clearly marked as example
}When to Fix (Always!)
❌ Real Credentials
// BAD: Real API key in workflow
{
"headers": {
"Authorization": "Bearer sk_live_abc123..."
}
// ❌ NEVER hardcode real credentials!
}
// GOOD: Use credentials system
{
"authentication": "headerAuth",
"credentials": {
"headerAuth": {
"id": "credential-id",
"name": "My API Key"
}
}
}Validation Profile Strategies
Strategy 1: Progressive Strictness
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:
validate_node({ nodeType: "nodes-base.slack", config, profile: "runtime" })
// errors + security/deprecation warnings; no best-practice advisoriesBefore deploying — surface the advisories you may want to act on:
validate_node({ nodeType: "nodes-base.slack", config, profile: "ai-friendly" })
// adds error-handling suggestions, rate-limit notes, outdated-typeVersion suggestionsHardening a critical workflow — the full lint:
validate_node({ nodeType: "nodes-base.slack", config, profile: "strict" })
// everything ai-friendly emits, plus "property won't be used" leftover checksStrategy 2: Profile by Workflow Type
Quick Automations:
Profile:
runtimeSee: errors + security/deprecation warnings
Fix: errors; skip best-practice advisories you don't need
Business-Critical Workflows:
Profile:
strictSee: every advisory
Fix: errors, security, and any advisory that applies to a production path
Integration Testing:
Profile:
minimalSee: only errors that would stop execution
Fix: those errors; everything else is out of scope while wiring connections
Decision Framework
Should I Fix This Warning?
┌─────────────────────────────────┐
│ Is it a SECURITY warning? │
├─────────────────────────────────┤
│ YES → Always fix │
│ NO → Continue │
└─────────────────────────────────┘
↓
┌─────────────────────────────────┐
│ Is this a production workflow? │
├─────────────────────────────────┤
│ YES → Continue │
│ NO → Probably acceptable │
└─────────────────────────────────┘
↓
┌─────────────────────────────────┐
│ Does it handle critical data? │
├─────────────────────────────────┤
│ YES → Fix the warning │
│ NO → Continue │
└─────────────────────────────────┘
↓
┌─────────────────────────────────┐
│ Is there a known workaround? │
├─────────────────────────────────┤
│ YES → Acceptable if documented │
│ NO → Fix the warning │
└─────────────────────────────────┘Documentation Template
When accepting a warning, document why:
// workflows/customer-notifications.json
{
"nodes": [{
"name": "Send Slack Notification",
"type": "n8n-nodes-base.slack",
"parameters": {
"channel": "#notifications"
// ACCEPTED WARNING: No error handling
// Reason: Non-critical notification, failures are acceptable
// Reviewed: 2025-10-20
// Reviewer: Engineering Team
}
}]
}Known false positives and blind spots (n8n-mcp 2.85.0)
Verified against a live n8n 2.38.5 instance and reported upstream. When a fix ships, the linked issue closes. Re-check before relying on this list.
False positives
| Validator says | Reality | What to do |
|---|---|---|
| ERROR "Incorrect error output configuration. Nodes "X" appear to be error handlers but are in main[0]…" | A name/type heuristic: fires when main[0] fans out and a target is respondToWebhook, emailSend, or has error / fail / catch / exception in its name | Only when the message matches exactly and you've inspected connections and confirmed X is on the success path by design (e.g. Respond to Webhook next to a "Save Order" node): keep the wiring, don't move X to main[1] or add continueErrorOutput (the webhook would answer only on failure), and say in your reply that you're ignoring #1111 and why. Don't run n8n_autofix_workflow with default fix types on it; exclude error-output-config. Renaming the node clears the name-based variant. Every other valid: false error still gets fixed. n8n-mcp#1111 |
| Warning "Possible missing $ prefix for variable (e.g., use $json instead of json)" | The check reads inside string literals, e.g. the JMESPath query "[?json.country=='PL'].json.name" over .all() items, where json. is required | Ignore it for words inside quotes. #1115 |
validate_node → "Code cannot be empty" (jsCode) on a Code node with language: "pythonNative" | validate_node doesn't recognize pythonNative, the only Python option in n8n 2.x | Validate the workflow (validate_workflow / n8n_validate_workflow) instead. #1112 |
Python "Return value must be a list of dicts" for return {"count": …} in Run Once for All Items | Native Python auto-wraps a single dict into one item | Accept, or return [{"json": {...}}] to silence it. #1113 |
Blind spots — validates clean, breaks at runtime
JS errors inside
{{ }}(TypeError on a missing path,JSON.parseon bad input, a thrownError) resolve tonulland the execution stays green. A Filter/IF with such a condition drops every item. See n8n-expression-syntax → Debugging.$jmespathmistakes inside expressions:country=="PL"(→[]), a bare numberrevenue > 100000, orandinstead of&&(→null). Only Code-node source is checked. #1114Native Python Code nodes: legacy
_input/_json/_node/_now,item.json.xdot access, any import not on the instance allowlist,class,type()/getattr(), dunder access,globalinside a function, and a list returned in each-item mode. #1113, see n8n-code-python.
For all three the cure is the same: a real test execution, then look at the output values, not just the status.
What the validator no longer flags
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:
Template literals inside expressions —
={{ `https://api/${$json.id}` }}is valid; n8n's engine evaluates full modern JS (template literals, optional chaining?.) inside{{ }}.Omitted
operationon 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;combinatorandconditions.optionsare optional (n8n defaults them); unary operators don't needsingleValue.Optional chaining, string-keyed bracket access (
$json['some-prop']), fields namedtest/null/undefined,this.helpersusage, regex$anchors, and bare-object returns inrunOnceForAllItemsmode.
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 (surface under every profile)
❌ Hardcoded credentials
❌ SQL injection risks
❌ Any error (
valid: false). These block activation, except the known false-positive errors listed above: check that table before "fixing" a correct workflow
Usually Fix
⚠️ Error handling (production)
⚠️ Retry logic (external APIs)
⚠️ Input validation (public webhooks)
⚠️ Rate limiting (high volume)
Often Acceptable
✅ Error handling (dev/test)
✅ Retry logic (internal APIs)
✅ Rate limiting (low volume)
✅ Query limits (small datasets)
Not a fix at all — advice, not defects
✅ Best-practice advisories under
ai-friendly/strictthat don't apply to your workflow (weigh them per-case using this document)✅ Outdated-
typeVersionsuggestions when your version is older-but-supported (that is normal n8n behavior)
Golden Rule: If you accept an advisory, document WHY.
Related Files:
SKILL.md - Main validation guide
ERROR_CATALOG.md - Error types and fixes
Requirements and compatibility
Works with: Claude Code, Claude apps, OpenAI Codex, GitHub Copilot, Cursor, Gemini CLI, Kiro, OpenCode, Goose, Amp, Roo Code, JetBrains Junie, Factory Droid, Devin (ex-Windsurf), Mistral Vibe, OpenHands, Letta
“Works with” means the tool reads the open Agent Skills format; a skill written for one tool may use features others lack.
Needs: Needs an MCP server, Needs Python, Needs Node.js
Security
No issues found by our static scan· No issues found
A static scan is not a guarantee. Skills are third-party content: review a skill before you let an agent use it.
Attribution and licence
- Created by
- Romuald Czlonkowski
- Publisher
- Romuald Czlonkowski (Community)
- Source
- czlonkowski/n8n-skills · skills/n8n-validation-expert · commit e037563
- Licence
- MIT License (redistribution allowed)
- LICENSE (added by AutoGineer from LICENSE)
- ATTRIBUTION.md (written by AutoGineer)
- Synced
AutoGineer is not affiliated with or endorsed by Romuald Czlonkowski or Romuald Czlonkowski.
Download ZIP
The ZIP holds the skill’s files, its licence files and an ATTRIBUTION.md. Download n8n-validation-expert.zip