N8n Workflow Lifecycle Official
n8nOfficialPublished by n8n
- Skill
- Workflow
- Automation and integrations
- Apache-2.0
- No issues found
AutoGineer summary
Written by AutoGineer from the skill’s own files, not by the author
This skill provides a structured six-stage methodology for managing n8n workflows from initial planning to production handoff. It emphasizes rigorous validation, proper documentation, and clear communication to ensure workflows are maintainable, reliable, and correctly configured. The framework covers technical best practices like node grouping, naming conventions, and folder management, while also addressing the human-in-the-loop requirements for credential verification and production readiness.
What it does
- Guides users through a six-stage lifecycle: Plan, Build, Validate, Test, Publish, and Handoff.
- Provides strict protocols for node naming, workflow descriptions, and visual organization using sticky notes and node groups.
- Enforces validation checks using both automated tools and manual verification steps before publishing.
- Manages folder organization and MCP access settings for workflow discoverability.
- Defines clear handoff procedures to ensure users can operate and monitor workflows in production.
- Outlines specific anti-patterns to avoid, such as incorrect assumptions about parallel execution or skipping testing.
When to use it
- When starting a new n8n workflow project from scratch.
- When organizing or refactoring existing workflows to improve readability and maintainability.
- Before deploying any workflow to a production environment.
- When troubleshooting workflows that are failing or behaving unexpectedly.
- When coordinating with users to ensure they have the necessary context to operate a finished workflow.
Advantages
- Reduces the likelihood of production failures by enforcing a mandatory testing and validation sequence.
- Improves long-term maintainability through standardized naming and documentation practices.
- Ensures workflows are properly organized and discoverable within the n8n instance.
- Clarifies the division of responsibility between the agent and the user, particularly regarding credentials and MCP access.
- Prevents common pitfalls like silent connection drops or incorrect assumptions about workflow execution order.
Usage examples
- “Design a new workflow to sync customer data from Postgres to Slack.”
- The agent initiates the 'Plan' stage, asking for specific requirements and checking for existing sub-workflows before drafting the structure.
- “I need to ship this workflow to production, is it ready?”
- The agent runs the validation checklist, verifies connections, asks the user to confirm credentials, and performs a test run before proceeding to publish.
- “Organize my messy workflow into something readable.”
- The agent applies node groups to logical steps, adds descriptive sticky notes, and updates the workflow description to capture the 'why' behind the design.
Install
Download the skill as a ZIP:
https://mp-staging.autogineer.com/agent-skills/n8n-io/n8n-workflow-lifecycle-official/downloadUnzip it into the cross-client skills folder of your project:
mkdir -p .agents/skills && unzip n8n-workflow-lifecycle-official.zip -d .agents/skills
Documentation by n8n
n8n Workflow Lifecycle
The six stages
PLAN. Gather requirements, ask clarifying questions, search for existing workflows / sub-workflows that already do this.
BUILD. Write SDK code (with skills: subworkflows, node-config, expressions, code-nodes; readability section below). Use
validate_node_configas a side-channel for iteration, debugging, or small single-node edits: clean per-parameter errors without full-graph noise. Not a replacement forvalidate_workflowin VALIDATE.VALIDATE.
validate_workflow+get_workflow_detailsfor connections, then have the user verify per-node credentials and create anything you couldn't (missing credentials, folders, etc).TEST.
test_workflowwithprepare_workflow_pin_data; iterate until output matches intent.PUBLISH.
publish_workflowonly after stages 3 and 4 are clean.HANDOFF. Production handoff: how to trigger it, what it returns, what to watch, what they should know to use it well.
Skipping a stage produces workflows that look done but break in production, or solve the wrong problem entirely. Three most common skips:
Build before plan. "User asked for X, I'll start coding" without confirming what X means, whether the same logic already exists as a sub-workflow, or which folder/project it belongs in. Cheaper to ask one clarifying question than to rebuild after.
Test before user-side wire-up. Running
test_workflowbefore the user has verified credentials per node hits the wrong service or 401s. Get the user-side setup done as part of VALIDATE.Publish without test. Validation passing means the SDK is well-formed; it does NOT mean the workflow is correct.
Non-negotiables
Validate AND verify before publish. Run
validate_workflowon the SDK code, thenget_workflow_detailsafter every create/update to check theconnectionsobject. Validation alone misses silently dropped wires.validate_node_configis a separate per-node iteration tool, not a replacement for this step.Surface known limitations to the user. If folders, MCP access, or any other limitation blocks the request, say so explicitly and propose a path. Don't silently dump workflows at the wrong location or report success on a request you couldn't fully fulfill.
Ask before testing when not-auto-pinned downstreams have side effects.
test_workflowauto-pins triggers, credentialed nodes, and HTTP Request nodes. Everything else (Code, Edit Fields, If, Wait, Execute Command, file ops, sub-workflow calls, Data Tables) runs for real. Ask the user before running if any of those would fire user-visible side effects. Seereferences/TESTING.md.
Strong defaults
Test before publish with
test_workflow+prepare_workflow_pin_data. Seereferences/TESTING.mdfor mocking by trigger type, pinning individual nodes, and the side-effect surface. Looser for internal one-off scripts you watch run.Always include a
descriptiononcreate_workflow_from_code. 1-2 sentences capturing what and why. See "Readability" below.
Validation isn't enough
validate_workflow runs schema and shape checks: missing parameters, type errors, references to non-existent nodes. It does not catch:
The
.to()-inside-.add()connection trap (silent dropped wires)Fan-outs collapsed to a single connection
Merge index off-by-one
Error outputs wired without
onError: 'continueErrorOutput'Parameters that are syntactically valid but semantically wrong (e.g., wrong sheet ID, wrong column name)
Validation is necessary but not sufficient. The real gate is:
validate_workflowpasses.get_workflow_detailsreturns aconnectionsobject that matches your intent.test_workflowproduces the right output on representative pinned data.
Only then call publish_workflow.
For the full pre-publish checklist, see references/VALIDATION_CHECKLIST.md.
Execution model
n8n workflows execute sequentially, left-to-right, top-to-bottom. Branches that visually appear parallel on the canvas (fan-out from one source to multiple downstreams) run one after the other, ordered by the target nodes' Y-position on the canvas. There is no automatic concurrency.
Practical consequences:
A fan-out to three slow HTTP calls runs in series; total latency is the sum, not the max.
"Parallel" branches share workflow state in execution order; downstream consumers see whatever the last branch left.
For real concurrency, dispatch sub-workflows with
mode: 'each'andwaitForSubWorkflow: false. Seen8n-loops-officialandn8n-subworkflows-official.
This is platform behavior, not an SDK quirk. Don't design fan-outs around assumed parallelism.
Naming conventions
Bad names compound: a workflow that's hard to find six months from now gets duplicated.
For full conventions (verb-noun patterns, capitalization, prefixes), read references/NAMING_CONVENTIONS.md. Short version:
Workflows: verb-first, scoped.
Send weekly customer reportnotCustomer report sender.Nodes: describe what they do in this workflow, not the node type.
Fetch active customersnotPostgres1.Sub-workflows: plain descriptive name (
Parse RFC2822 date); carry the category in tags (subworkflow, a domain tag,tool), not a name prefix.search_workflows({ tags })filters on them. Seen8n-subworkflows-officialreferences/NAMING_AND_DISCOVERY.md.Tags: the AI-side discovery mechanism (n8n 2.27.0+). The MCP lists (
list_workflow_tags), filters (search_workflows({ tags })), and attaches them (update_workflowaddTags/removeTags, auto-creating unknown names). Lowercase, 2-4 per workflow. Seereferences/NAMING_CONVENTIONS.md.
Readability: descriptions, node groups, sticky notes, conventions
Four levers carry the readability load, grouping from 7 boxes on and the other three from about 5 nodes on:
Workflow
description: capture the why, including AI-derived context. Two sentences: what it does and why it exists. Most importantly, capture context you had during conversation that won't otherwise survive into the file (the constraint that drove the design, why this approach over the alternative, the user's reason for asking). Otherwise it dies with the chat.Node groups: the only way to group nodes. Named frames, one per logical step (
Validate input,Enrich order,Notify). Organization only, members run inline (reuse/isolation is a sub-workflow's job). Readreferences/NODE_GROUPS.mdbefore creating or editing any group, and after any save that reported a group problem. The three things to know up front:When: more than 7 boxes with every group collapsed, typically into 3-5 groups. A box is the trigger, each group, and each ungrouped node; sub-nodes and sticky notes don't count.
A group must be a connected, trigger-free section with at most one member facing out in each direction, with an Agent's sub-nodes all in or all out. Members are node names, not ids.
An invalid group op is skipped, not fatal, and unrelated edits can silently delete a valid existing group, so a save that looks clean can have lost one. The exception is a description over 145 characters: that rejects the whole
update_workflowcall. Read the group fields on every create and update, and repair before reporting done.
Sticky notes: annotate, don't group. Grouping is the node group's job (above). Stickies carry what a group name can't: a callout, a TODO, a warning. Read
references/STICKY_NOTES.mdbefore placing one.On create, always anchor.
sticky(content, [nodes], { color })derives the box from the nodes it wraps, after layout. Coordinates guess at a canvas that doesn't exist yet; omitting both is worse, since the fallback centers the note under the whole graph and only dodges other stickies, never nodes. If you can't name the nodes a note is about, it doesn't belong on the canvas.Three roles, and the anchor set follows from the role. A stage zone anchors one stage's nodes; a callout anchors the single node it warns about; a gray title band anchors the whole flow and is rare, only for a canvas you're deliberately leaving ungrouped.
Scale: under ~5 nodes, none. From 5 to 7 boxes, one zone per stage. Over 7, node groups section and document the canvas via their descriptions, and you anchor nothing to a grouped node: those positions are collapsed-chip slots, so the note draws over the expanded group and matches nothing collapsed.
Zones need room the derived box doesn't give. Declare
height: 272for a one-row zone,464for a branch fan (add 32 for Message an Agent v2+). Never declare a width. Then, once the canvas is final, widen every zone in one batchedupdate_workflow:x = min(node.x) - 56,width = span + 112. Computed from the nodes, so re-running is harmless, but it's a finishing step, not per-edit.update_workflowhas no sticky op. A sticky there is anaddNodeofn8n-nodes-base.stickyNotewithcontent,width,height,colorinparameters; an omittedpositionmeans[0, 0].Content and color:
### Titlecarrying the point, plus one short line. Only the top 64px clears the nodes, so never add height for more text; longer prose goes in nodenotesor the workflowdescription.color1-7 stands for what happens there: green creates or involves a person, red is destructive or irreversible, blue is routing and transformation, gray is documentation. A user preference, then nearby workflows, outrank that.
Node
notesfor non-obvious config. Explain why a workaround exists or a Code node does what it does. Don't annotate obvious nodes.
Plus two notes:
Match existing project conventions before introducing your own. Skim a couple of nearby workflows via
search_workflows+get_workflow_detailsand mirror the sticky palette, naming, and description style.Layout runs on create, never on update.
create_workflow_from_codelays out every node that doesn't carry an explicitposition, so omit them and let it. Setting some and not others is the bad case: yours are honored and the rest are laid out around them, which is how nodes end up stacked.update_workflowruns no layout at all, so anything you add sits exactly where you put it.
Folder management
On a registered instance the MCP creates and organizes folders: create_folder, update_folder (rename/move), move_workflows_to_folder, and a folderId on create_workflow_from_code for create-time placement. search_folders resolves names to IDs.
If the user wants a folder that doesn't exist, create it, don't build at the root and report success. If the folder tools are absent, the instance isn't registered: folders are blocked in the UI too, so ask the user to register (free, in Settings) rather than create the folder by hand. No tool deletes a folder, and projects are read-only.
For the full protocol, read references/FOLDER_LIMITATIONS.md.
Per-workflow MCP access
Each workflow has an availableInMCP flag. The default depends on who created it:
Workflows created via the MCP (
create_workflow_from_code) default to MCP-accessible. No toggle step needed: you can find them viasearch_workflowsand operate on them right away.Workflows created in the n8n UI can default to off. Until the user flips the toggle, the workflow is invisible to you.
The #1 case where this bites: the user built a workflow manually in the UI and now wants you to inspect or edit it, but you can't see it. Before assuming it doesn't exist or you're searching the wrong project, ask the user to confirm MCP access is enabled.
Sub-workflows called via MCP: the caller can use them as code-level sub-workflows without the toggle. To invoke as MCP-exposed tools, the toggle is required (and is on by default for MCP-created sub-workflows).
For the full case-by-case guide and user-facing message, read references/MCP_ACCESS_PER_WORKFLOW.md.
User-side wire-up (part of stage 3)
There are things the user has to do that you can't, and they need to be done before testing, otherwise the test fires against the wrong credential, hits a missing folder, or 401s. Surface these as a short list during VALIDATE, before TEST:
Verify credentials per node.
newCredential('Label')is cosmetic. n8n auto-assigns the most recently edited credential of the right type, which silently picks the wrong one when the user has multiples (prod vs staging Gmail, two API keys). Tell them: "open every node that uses a credential and confirm the right one is selected." Seen8n-credentials-and-security-officialnon-negotiable #2.Create missing credentials. If the user pasted a secret in chat or the workflow needs an account that doesn't exist yet, name the credential type and have them create it in the UI.
Register for folders (only if the tools are missing). Folder tools need a registered instance. If they're absent, the user registers (free, in Settings) before you can create or place folders; otherwise you handle folders yourself. See
references/FOLDER_LIMITATIONS.md.MCP access toggle for user created workflows. Workflows you create via the MCP are MCP-accessible by default. The toggle only matters when the test depends on a UI-created workflow being callable from the MCP. See
references/MCP_ACCESS_PER_WORKFLOW.md.
Don't proceed to TEST until these are confirmed done.
Handoff: production handoff (stage 6)
After publish_workflow and a clean test, the workflow is technically live, but the user still needs enough context to actually use it in production. Treat this like the freelancer-to-customer handoff: short, structured, and oriented toward how they'll operate it from here.
What to include:
How it triggers. Webhook URL (live now that it's published), schedule cadence + timezone, manual trigger button, sub-workflow caller, whichever applies. For webhooks, hand them the URL.
What it returns / where the data goes. One sentence. "Writes new rows to the
customerstable," "responds JSON to the caller," "fires the on-call Slack channel."How to invoke it for real, with an example. "Hit the webhook with
curl -X POST <url> -d '{...}'," "trigger manually from the UI," "wait until 09:00 UTC for the first scheduled run."What to watch. Failure modes that surface as alerts/errors, rate-limit ceilings on upstream services, and where to look first when something breaks (executions tab, error workflow, audit log, etc.).
MCP access status. If you created the workflow via the MCP, it's already MCP-accessible. Let the user know they can revoke access in Settings if they want to lock it down. If they hand-built it in the UI, they need to flip MCP access on for any other agent to call it.
Anything still pending on their side. Secret rotation if a token was pasted in chat, follow-up wiring you couldn't reach, known TODOs left in stickies.
Keep it tight: half a dozen bullets, not a wall of text. The user shouldn't have to ask "ok, what now?"
Reference files
| File | Read when |
|---|---|
references/NAMING_CONVENTIONS.md | Naming a new workflow, sub-workflow, or node |
references/NODE_GROUPS.md | Before creating or editing any node group, or after a save reported a skipped or removed group |
references/STICKY_NOTES.md | About to add, move, or resize a sticky note on either tool |
references/FOLDER_LIMITATIONS.md | User mentions a folder, project structure, or wants workflows organized |
references/MCP_ACCESS_PER_WORKFLOW.md | Building a workflow that you or another agent will call via MCP |
references/VALIDATION_CHECKLIST.md | Just finished a workflow and about to call publish_workflow |
references/REVIEW_CHECKLIST.md | Reviewing or auditing an existing workflow (any age, any author). Severity-tiered findings, distinct from the pre-publish validation checklist |
references/TESTING.md | About to run test_workflow or execute_workflow, mocking trigger input, side-effect protocol |
Anti-patterns
| Anti-pattern | What goes wrong | Fix |
|---|---|---|
Calling publish_workflow without validating | Broken workflows reach production | Validate, verify connections, then test |
| Creating workflows at root because the requested folder doesn't exist | Workflows get lost, and the user has to drag them manually | Surface the limitation before building |
Generic node names (HTTP Request1, Set2) | Workflows are unreadable a month later | Rename to describe the action |
Missing description on create_workflow_from_code | Workflow invisible in search, no context for maintainers | Always include 1-2 sentences |
| Asking the user to flip the MCP access toggle on a workflow you created via the MCP | Wastes their time, agent-created workflows default to MCP-accessible | Only mention the toggle for UI-created workflows, or when the user wants to revoke MCP access on an agent-created one |
Running test_workflow on a workflow with side-effecty non-pinned downstreams without asking | Real Data Table write, real sub-workflow side effects, real Execute Command output, etc. Triggers + credentialed nodes + HTTP get pinned, nothing else does | Ask first. See references/TESTING.md. |
| No node groups on a canvas over 7 top-level boxes | Reader has to read every node to find what they want | Group each logical step. See references/NODE_GROUPS.md |
Reporting a create or update as done without reading its group fields (skippedGroups on create; skippedOperations / removedGroups on update) | A group op was silently skipped, or an existing group was deleted by an unrelated edit, and the save still looked successful | Check them on every call and repair. See references/NODE_GROUPS.md |
| Sticky note used to fake a group, or sticky-of-every-color | Grouping is the node group's job / color becomes pure noise | Group with a node group; reserve stickies for callouts, one color per category |
Hand-positioned stickies on create_workflow_from_code | Coordinates are chosen before layout runs, so the note lands away from the nodes it describes | Pass the nodes to sticky(content, [nodes]) and let the box be derived. See references/STICKY_NOTES.md |
description: "Sends Slack." | Adds nothing visible from the trigger and Slack node | Include why + AI-derived context: "Sends weekly summary to founders. Replaces manual report that kept getting skipped." |
| Designing fan-out branches as if they execute concurrently | n8n runs fan-out branches sequentially, top-to-bottom by Y-position. Total runtime is the sum of branches, not the max | For real concurrency, dispatch via Execute Workflow with mode: 'each' + waitForSubWorkflow: false. See n8n-subworkflows-official "Fire-and-forget parallelization" |
Files
- ATTRIBUTION.mdAdded by AutoGineer868 B
- LICENSELicence file added by AutoGineer11.0 KiB
- SKILL.md19.1 KiB
- references/FOLDER_LIMITATIONS.md1.7 KiB
- references/MCP_ACCESS_PER_WORKFLOW.md1.6 KiB
- references/NAMING_CONVENTIONS.md5.6 KiB
- references/NODE_GROUPS.md5.3 KiB
- references/REVIEW_CHECKLIST.md38.5 KiB
- references/STICKY_NOTES.md9.4 KiB
- references/TESTING.md8.0 KiB
- references/VALIDATION_CHECKLIST.md10.7 KiB
references/REVIEW_CHECKLIST.md
Workflow review checklist
Severity-tiered audit for any existing n8n workflow or n8n build (group of workflows). Different from VALIDATION_CHECKLIST.md (which is pre-publish gates for your own in-progress build): this file is for reviewing arbitrary workflows, including ones built by anyone, from anywhere.
How to use
Walk the list top to bottom. For each item, inspect the workflow (get_workflow_details) and decide if the issue applies. Report findings grouped by severity. Each item links to the canonical skill reference for the why and the how to fix. Be a very thourough reviewer and air on the side of reading references to ensure full context.
You're reviewing JSON, not SDK source. get_workflow_details returns the n8n workflow JSON (nodes with parameters, connections graph, credential references, node type strings like n8n-nodes-base.httpRequest). Phrase findings in JSON terms: "node Foo has parameters.onError set to 'continueErrorOutput' but connections.Foo.main[1] is empty,".
| Severity | Meaning | Action |
|---|---|---|
| MUST FIX | Ship-blocker. Security hole, broken connection, or production-breaking bug. | Stop the workflow if active, fix before re-enabling. |
| SHOULD FIX | Real issue. Antipattern, missing error handling on production paths, broken contracts. | Plan a follow-up, fix in the next change. |
| NICE TO HAVE | Polish. Naming, descriptions, conventions. | Clean up opportunistically. |
Cross-cutting first
Before walking the per-domain list:
[ ] Pull the workflow(s).
get_workflow_details({ workflowId }), required so subsequent checks operate on the actual JSON, not assumptions.[ ] High-level intent / logic smell test. Read the workflow's
description(if it exists), then trace the happy path once top to bottom. Does the structure match what the description says it does? Anything obviously dead, missing, contradictory, or not fitting (a write node in a workflow described as read-only, a fan-out with one terminal branch that should be wired further, an HTTP call to a domain unrelated to the stated integration)? Catches whole classes of issues the per-domain checks won't surface.[ ] Note the trigger type. Webhook, schedule, manual, sub-workflow, chat trigger. Severity of issues changes by trigger (a webhook-API workflow needs error paths; a manual run does not).
[ ] Note whether the workflow is active. Active/published workflows with broken connections are higher severity.
MUST FIX
Credentials and secrets
[ ] Tokens, API keys, or secrets in node text fields. Any node parameter holding
Bearer xxx,sk-..., an API key, or a password as plain text. The credential system is the only correct home. → n8n-credentials-and-security-official[ ] Tokens stored in Set node values for later
{{$json.token}}referencing. The token is in workflow JSON regardless of how it's read. → n8n-credentials-and-security-official[ ] Hardcoded credentials in Code nodes. Same leak surface as text fields. → n8n-code-nodes-official anti-patterns
[ ] HTTP Request nodes with
Authorizationheader values typed in directly instead of using a credential. ForAuthorization: Bearer <token>, useBearer Auth(httpBearerAuth) so the token is stored without the prefix. For other custom auth headers, useHeader Auth(httpHeaderAuth). → HTTP_REQUEST_WITH_AUTH.md[ ] Secret read from
$vars.Xand used as an auth value. Use a credential instead. → n8n-credentials-and-security-official
SQL / query injection
[ ] User input interpolated into a query string. Any DB node with
parameters.querycontaining{{ $json.something }}(or any{{ ... }}expression that resolves from caller input) inside the SQL itself is a SQL injection: n8n substitutes the expression into the query before parameter binding. Use parameter binding instead (Postgres / MySQL:$1, $2placeholders +parameters.options.queryReplacement; Mongo: object filters). → DATABASE_NODES.md
Connection bugs (silent breakage)
[ ] Merge index off-by-one.
parameters.useDataOfInputis 1-indexed but the corresponding entry inconnections.<source>.main[index]is 0-indexed. If the merge node's expected primary input doesn't match the wiring, the wrong source is picked silently. → MERGE_NODE.md[ ] Merge with 3+ sources but
numberOfInputsleft at default 2. Third source silently drops. → MERGE_NODE.md[ ] Error output wired without
onError: 'continueErrorOutput'on the node config. Error branch is unreachable; node failure halts the workflow. → NODE_ERROR_OUTPUTS.md[ ]
onError: 'continueErrorOutput'set butmain[1]not wired. Error path is enabled but goes nowhere. → NODE_ERROR_OUTPUTS.md
Webhook API workflows (Webhook + Respond to Webhook)
[ ] Webhook performs a sensitive action with
parameters.authentication: 'none'. "Sensitive" = mutates state, sends external messages, hits production data, exposes private info, triggers paid actions. Anyone with the URL can fire it. Setparameters.authenticationto'basicAuth'or'headerAuth'and use the matching credential. → n8n-credentials-and-security-official
Sub-workflow contracts
[ ]
Execute Workflow Triggerset topassthroughwhen it shouldn't be. Passthrough loses the typed-input contract that agent tools (fromAi()) and structured callers need. Only correct when (a) the sub-workflow specifically receives binary AND isn't an agent tool, or (b) the sub-workflow takes no inputs (Define Below requires at least one field). For (b), the body should open with aSet("Keep Only Set", no fields) and the trigger should carry a sticky noting no inputs are expected. → n8n-subworkflows-official non-negotiables and SUBWORKFLOW_PATTERNS.md "Splitting by input shape"
First-class n8n Agents
Review an Agent with get_agent (draft) and get_agent with versionId set to its published activeVersionId (live). Findings are in config JSON terms.
[ ] No
memoryblock on a conversational Agent. It forgets the previous turn even inside one session. Add{ "enabled": true, "storage": "n8n" }. → MEMORY.md[ ] Saved sub-agent unpublished while the parent is published. Production delegation fails and the parent answers without it. Nothing else flags this. → SUB_AGENTS.md
[ ] Tool that sends, pays, deletes, or changes data without
requireApproval(or MCPapproval). → APPROVALS.md[ ] Approval-required tool on an Agent that runs from Message an Agent or a scheduled task. Fails, or parks with no one to answer. → APPROVALS.md
[ ] Telegram channel with
accessMode: publicwhen the Agent isn't meant for strangers. Anyone can talk to it and spend its budget. → CHANNELS.md[ ] Two different Agents called with the same Message an Agent session key in one project (for example both keyed on a bare Telegram id). Keys are project-wide, not per Agent, so the second Agent's call fails with "Failed to create the agent execution record". Prefix keys per Agent. → MESSAGE_AN_AGENT.md
Chat-triggered agents (Slack / Discord / Teams / Telegram)
[ ] Bot's own user ID not filtered out, either via the trigger's own filter option (preferred: Slack's
options.userIdsexclusion list) or as the first node after the trigger. The bot's reply re-triggers the workflow → infinite loop. Watch out for surface-specific semantics: Telegram'suserIdsis an allowlist, not an exclusion list. → CHAT_SURFACE_PATTERNS.md
SHOULD FIX
Naming
[ ] Generic node names (
HTTP Request1,Set2,Postgres1). Debugging-hostile: a failure onnode "HTTP Request3"tells the operator nothing, butnode "Fetch order details"localizes the break instantly. Rename every node to describe what it does in this workflow. → NAMING_CONVENTIONS.md
Comms nodes (Slack, Gmail, Discord, SMTP, Telegram)
[ ] n8n attribution still appended on Slack / Gmail / Email / Discord nodes. Most comms nodes have "Append n8n Attribution" enabled by default. Users typically want it removed in production. → COMMS_NODES.md
[ ] Slack thread reply posting as top-level message.
thread_tsnot set, or set in the wrong nested location. → COMMS_NODES.md
Set node antipattern
[ ] Set node feeding only 0 or 1 downstream consumer. Most common antipattern in the pack. Delete + inline the expression at the consumer. → n8n-expressions-official "The Set-node antipattern"
[ ] Set node before a Data Table Insert/Update mapping fields to schema. Map directly in the Data Table node's per-column expression slots. → n8n-data-tables-official strong defaults
[ ] Set node building an email/Slack body. Build the body inline in the comms node's body field with an expression. → COMMS_NODES.md
[ ] Multiple consecutive Set nodes each defining one field. Collapse, or eliminate. → n8n-expressions-official
Code node antipattern
[ ] Code node doing pure data shaping (
.map,.filter,.find, field rename, optional chaining). Use an expression or Edit Fields with arrow function. → n8n-code-nodes-official decision tree[ ] Code node using
crypto.createHash/crypto.createHmac. Use the native Crypto node (n8n-nodes-base.crypto). Recurring AI slip. → n8n-code-nodes-official "Cryptographic operations"[ ] Code node parsing XML / SOAP / RSS. Use the native XML node + Edit Fields with arrow function for extraction. → n8n-code-nodes-official "XML / SOAP / RSS parsing"
[ ] Code node + Set node combo (Set builds inputs, Code transforms). One Edit Fields with arrow function does both. → ARROW_FUNCTIONS_IN_EDIT_FIELDS.md
Expression discipline
[ ]
$json.xreferences deep in workflows with branches/intermediates. Switch to$('Source Node').item.json.xfor refactor stability. → n8n-expressions-official non-negotiable[ ] DateTime nodes used for date math/formatting. Use Luxon expressions (
DateTime.fromISO(...)) inline. → n8n-expressions-official strong defaults[ ]
$env.Xreferenced in any expression. Doesn't work, throws at runtime. Replace with$vars.X(paid plans), a Data Table, or a credential for secrets. → n8n-expressions-official anti-patterns[ ] Aggregate node + per-item execution mismatch. Expressions using
$input.all()/$('Node').all()without combining with another node's.itemshould setexecuteOnce: trueon the node. → n8n-loops-official non-negotiable and n8n-expressions-official ".all().map() triggers an executeOnce question"
Execution model
[ ] Workflow assumes fan-out branches execute in parallel. They don't, n8n runs them sequentially top-to-bottom by Y-position. Real concurrency needs sub-workflow dispatch with
mode: 'each'+waitForSubWorkflow: false. → n8n-workflow-lifecycle-official "Execution model"
Loops
[ ]
Loop Over Itemsadded "to make it loop" when default per-item iteration handles it. Default per-item iteration already waits for each item before the next, so a Loop Over Items added "to wait for all items" is unnecessary. → n8n-loops-official "When NOT to reach for Loop Over Items"[ ] Custom pagination implementation (Loop Over Items +
$pageCount, hand-rolledwhilein a Code node, Set + IF cycle, etc.) instead of HTTP Request's built-inPaginationoption. → HTTP_PAGINATION.md[ ] Reset-mode loop with no clear termination.
reset: truewithout an explicit stop condition +$runIndexceiling = infinite loop, n8n eats memory until killed. → LOOP_OVER_ITEMS.md "Reset mode"[ ] One
Loop Over Itemsnested inside another in the same workflow. Doesn't work; breaks at runtime. Move the inner loop into a sub-workflow called per outer iteration (mode: 'each'). → LOOP_OVER_ITEMS.md "Nesting Loop Over Items"
Self-healing on transient failures
[ ] Network-calling nodes (HTTP, comms, DB, AI) without
retryOnFailconfigured. Transient 429s and upstream blips surface as 5xx, alerts fire on noise. → n8n-error-handling-official "Self-healing on transient failures"
Switch nodes
[ ] Switch with no fallback output configured. Unmatched items silently drop. Set
options.fallbackOutput: 'extra'andoptions.renameFallbackOutput: '<name>'. → SWITCH_FALLBACK.md[ ] Switch outputs unnamed. Set
renameOutput: true+outputKey: '<name>'per rule for self-documenting branches. → SWITCH_FALLBACK.md
Sub-workflows
[ ] Duplicated logic across workflows that would be a sub-workflow. → n8n-subworkflows-official decision tree
[ ] Sub-workflow with no
description. Won't be found in future searches; nobody (or AI) knows what it does. → n8n-subworkflows-official anti-patterns[ ] Sub-workflow has hidden side effects. The name and
descriptiondescribe pure logic (parse, validate, format, compute, transform), but the body contains write / send nodes (Data Table Insert/Update, Slack/Gmail/Discord send, HTTP POST, file write, audit log, etc.). Callers reasonably assume the sub-workflow is safe to retry; doing so creates duplicate writes or sends. Either declare the side effect (rename to e.g.Audit:or<Domain>:, document it indescription, return a result the caller can branch on) or move the side effect out of this sub-workflow. → n8n-subworkflows-official "Stateless vs stateful"[ ] ~30-node workflow with no extraction. Extract logical sections into sub-workflows. → n8n-subworkflows-official
AI Agents and tools
[ ]
options.maxIterationsleft at default 10 on a multi-tool agent. Likely too low for modern agents with flexible tool sets; throws "Max iterations reached" workflow error. Raise to 30-50+. → AI_NODES.md "Iteration cap"[ ] Generic tool names (
doStuff,runQuery). Model can't tell which tool to pick, skips them or hallucinates parameters. Use verb-first specific names. → TOOLS.md[ ] Default, Empty, or one-line tool descriptions. Model has no clue when to invoke. Tool descriptions are part of the prompt. → TOOLS.md
[ ]
outputParserStructuredwithoutautoFix: true. One bad model output and the workflow fails. SetautoFix: truewith a coding-capable fixer model. → STRUCTURED_OUTPUT.md[ ] Tools with user-visible side effects (send, pay, refund) without human review. Wrap with
slackHitlTool/discordHitlTool/telegramHitlTool/gmailHitlTool/ etc. → HUMAN_REVIEW.md[ ] Approval message via
fromAi()instead of$tool.parameters.<name>. Model paraphrases; you approve text not values. → HUMAN_REVIEW.md[ ] Hardcoded
sessionId: 'default'or no sessionId on memory. All conversations share one session or sessions won't be used properly. → MEMORY.md[ ] Image / audio / video generation wrapped in an Agent. Binary doesn't flow through tools or the Agent's output formatter. Use the provider's native single-call node directly. → n8n-ai-nodes-official anti-patterns
[ ] Agent + Switch to route on natural-language input when Text Classifier (
@n8n/n8n-nodes-langchain.textClassifier) is one node with N built-in branches. → n8n-ai-nodes-official[ ] Agent (or Basic LLM Chain + structured output parser) used to pull fields out of a blob of text when Information Extractor (
@n8n/n8n-nodes-langchain.informationExtractor) is one node with a typed schema, no tools, no system prompt. Use the Agent only when the extraction needs tool calls or multi-turn reasoning. → n8n-ai-nodes-official
First-class n8n Agents: quality
[ ] Node tool without a
descriptionin its JSON. It gets a generic "Execute node" text and is picked badly. → TOOLS.md[ ] Tool
expressioninput that reads trigger or upstream data ($('Webhook'),$json.<event field>). Tools only see the model's arguments, so it's empty or wrong. → TOOLS.md "What expressions can see"[ ] Vague
useWhenon sub-agents or vector stores, or skill descriptions that don't say "Use when ...". These are the triggers; vague ones never fire. → SKILLS.md[ ] Task with no
timezone, or an objective that doesn't say where results go. Fires at the wrong hour, or produces output nobody sees. → TASKS.md[ ] Slack approval where the approver needs the values (amount, recipient). Channel cards don't show parameters. → APPROVALS.md
[ ] MCP server without
toolFilter, exposing a whole catalog when the Agent needs a few tools. → TOOLS.md
System prompts (AI Agents)
The system prompt is the load-bearing config of an agent. Severity ranges by how badly the issue degrades behavior.
[ ] Hardcoded date / missing date in the system prompt. Stale immediately. Agents making time-sensitive decisions (deadlines, eligibility windows, "is X expired?") get wrong answers. Use
Current date: {{ $now }}. → SYSTEM_PROMPT.md "Always include the current date"[ ] Long system prompt with per-tool usage instructions buried inside. Modular split violation. Brittle to edit, and tools become unreusable across agents. Move tool-specific instructions into each tool's description. → SYSTEM_PROMPT.md "The modular split" and n8n-ai-nodes-official "What goes in the system prompt vs the tool description"
[ ] "You are a helpful assistant" preamble with no role specifics. Generic responses, agent has no identity. Replace with a specific role and scope. → SYSTEM_PROMPT.md "What it's for"
[ ] No refusal/safety boundary when one is needed (the agent has tools that touch user-visible state, sends, payments). Define explicit boundaries: "only answer questions about X, redirect otherwise". → SYSTEM_PROMPT.md
Data Tables
System columns and identifiers
[ ] Set node before Data Table Insert/Update. Map directly in per-column slots. → n8n-data-tables-official strong defaults
[ ]
id,createdAt, orupdatedAtdeclared increate_data_table. System-managed; declaring them errors or shadows. → n8n-data-tables-official non-negotiable[ ] Auto-
idused as a cross-system identifier. Resets on table recreate / instance migration. Add a domain ID column (arxivId,customerId,requestId). → SCHEMA_DESIGN.md "system-managed columns"
_object postfix discipline (the most error-prone area)
[ ] Column holds stringified JSON (array, object) without the
_objectpostfix. Readers have no contract telling them to parse. → SCHEMA_DESIGN.md "the_objectconvention"[ ] Column has
_objectpostfix but isstringtype holding native (non-stringified) values. Contract violation: postfix promises stringified content. → SCHEMA_DESIGN.md[ ] Insert/Update writes to an
_objectcolumn withoutJSON.stringify(...). A[object Object]literal lands in the row. → SCHEMA_DESIGN.md[ ] Read from an
_objectcolumn withoutJSON.parse(...). Downstream gets a string where it expects array/object; templates and tools choke. → SCHEMA_DESIGN.md[ ] Sub-workflow returns
_objectcolumns as strings to callers. Storage format leaking through the interface. Parse before returning so callers receive arrays as arrays and objects as objects. → SCHEMA_DESIGN.md "Storage format ≠ interface format"[ ] Sub-workflow's "fresh" path stringifies to "match" the cached path. Wrong instinct. Parse the cached path so both branches return natural shapes. → SCHEMA_DESIGN.md
[ ]
_objectcolumn holds data that needs to be queried (filter ontopicscontent, find rows by tag). Strings can't be queried structurally. Refactor to a relational child table or move to a real DB. → SCHEMA_DESIGN.md "When NOT to use_object"
Schema design
[ ] String column acting as boolean (
'yes'/'no','true'/'false'). Use thebooleantype. → SCHEMA_DESIGN.md "Picking column types"[ ] Mixed casing within one table or query (
createdAtANDarxiv_idtogether). Match camelCase to the system columns. → n8n-data-tables-official strong defaults[ ] Table named with snake_case, lowercase, or in singular for a set. Title Case with spaces (
Papers,Recent Events). Plural for sets, singular for one-row-per-global-thing only. → SCHEMA_DESIGN.md "Naming"[ ] Boolean column not named affirmatively (
completedinstead ofisCompleted). Affirmative names read better in filters and IFs. → SCHEMA_DESIGN.md[ ] Table with more than ~17 columns. Consider splitting; the table is probably trying to be multiple things. → SCHEMA_DESIGN.md "A healthy table"
[ ] User-declared
datecolumn duplicatescreatedAt/updatedAt. Use the system columns where they fit; only add explicitdatecolumns when the timestamp's meaning differs from "row created" or "row updated". → SCHEMA_DESIGN.md
Relational design (when the data has shape)
[ ] Genuine parent-child data crammed into one wide table (papers + summaries in one
Paperstable, customers + orders in oneCustomerstable). Split into parent + child tables, reference parent byid(paperId,customerId). → SCHEMA_DESIGN.md "Designing relationally" and n8n-data-tables-official "Relational design"[ ] Child arrays stored in
_objectcolumns when they need to be queried or filtered individually. Refactor to a relational child table. → SCHEMA_DESIGN.md "When NOT to use_object"[ ] Cascade strategy unclear (no defined behavior on parent delete). Pick one per relationship: cascade-delete via a separate Delete on children, soft-delete (
archivedflag), or orphan. Mixed strategies cause silent bugs. → SCHEMA_DESIGN.md "Enforce integrity in the workflow"[ ] Multi-table write sequence (parent Insert + child Insert) without partial-failure handling. No transactions exist; a child failure leaves the parent orphaned. Pick: compensating writes, idempotent retry with
upsert+ stable domain IDs, or soft state marker (status: 'pending' → 'complete'). → SCHEMA_DESIGN.md "No transactions, plan for partial failure"[ ] Child rows pointing at deleted parents (orphan detection missing). Either soft-delete by default, or run a periodic cleanup workflow. → SCHEMA_DESIGN.md "Watch for child without parent"
[ ] 3+ joined tables with transactional writes. Past Data Tables' wheelhouse. Use a real SQL DB. → SCHEMA_DESIGN.md "When Data Tables are the wrong tool"
Operation gotchas
[ ] Multi-column filter without explicit
matchType: 'allConditions'. Defaults toanyCondition(OR) in some versions; surprising when intent is AND. → OPERATIONS.md "matchType"[ ]
GetwithoutalwaysOutputData: truefollowed by an IF that branches on result presence. No-match produces zero items, the IF doesn't fire at all. → DEDUP_PATTERNS.md "Pattern 3: Get + IF"[ ] Get + IF branches produce different JSON shapes (cached path vs. freshly-processed path). Downstream
$json.xresolves to the wrong field depending on which fired. Insert a Set/NoOp anchor to normalize. → n8n-expressions-official "Combine Inputs convergence"[ ]
Updatewith no match silently does nothing. No error. Either follow with aGetto confirm, or useupsertfor create-or-update. → OPERATIONS.md "Update with no match"[ ]
Insertwith no dedup in a workflow that can re-fire (webhook retry, scheduled re-run). Creates duplicates. Useupsert,rowNotExists+ Insert, or upstream dedup. → DEDUP_PATTERNS.md[ ]
returnAll: falsewithout explicitlimit. Defaults to 50 in many versions; downstream may expect more and silently truncate. → OPERATIONS.md "returnAll"[ ] Plain "have I seen this value?" dedup using a Data Table. The
Remove Duplicatesnode ("items seen in previous executions" mode) handles this with no schema. Reach for Data Tables only when the dedup state needs to be queryable, has row-level logic, or has TTL/per-tenant scoping. → DEDUP_PATTERNS.md[ ] Idempotency-key workflow without TTL cleanup. Markers shouldn't live forever; run a daily cleanup. → DEDUP_PATTERNS.md "Idempotency keys"
Mapping mode and evolution
[ ]
autoMapInputDatamode without a stable 1:1 between upstream field names and column names. Drift on either side silently breaks the mapping. Default todefineBelow. → SCHEMA_DESIGN.md "Mapping mode"[ ] Workflow attempts an in-place column type change. Not supported. Add a new column, copy via workflow, drop the old, rename the new. → SCHEMA_DESIGN.md "Schema evolution"
Wrong tool for the job
[ ] Cross-app shared data in Data Tables. Awkward to query from outside n8n. Use a real DB. → SCHEMA_DESIGN.md "When Data Tables are the wrong tool"
[ ] Millions of rows or write-heavy volume in a Data Table. Performance degrades; use a real DB. → SCHEMA_DESIGN.md
Binary handling
[ ] Reading file content from
$jsoninstead of$binary. → BINARY_BASICS.md[ ] Agent tool returning raw binary directly. Tool output is JSON-only. Upload to storage, return key/URL in JSON. → AGENT_TOOL_BINARY.md
[ ] Uploaded chat files passed to a tool via
fromAi.fromAidoesn't carry binary. Pre-stage to storage, inject keys in the system prompt. → AGENT_TOOL_BINARY.md[ ] Binary lost after a JSON transform. Use Merge to combine the JSON output with the binary stream. → MERGE_FOR_CONTEXT.md
[ ] Image sent to a chat surface from raw
$binary. Chat surfaces need a URL-referenced image (or platform-native file upload). → CDN_REQUIREMENT.md
Public trigger auth
[ ] Webhook trigger with
parameters.authentication: 'none'even on read-only / lookup paths. The URL is publicly callable; no auth invites abuse (rate-limit exhaustion, scanning, scraping). Use Basic / Header / JWT auth unless the workflow is genuinely meant to be public. (If the action is sensitive, this is a MUST FIX, see above.) → n8n-credentials-and-security-official[ ] Form Trigger fronting a sensitive action with no auth gate. Forms that create accounts, mutate data, send external messages, or trigger paid operations should require real auth at the trigger. An obscure URL is not auth. → n8n-credentials-and-security-official
Webhook / Respond to Webhook
[ ] Fallible nodes with no error path. HTTP / DB / API / file nodes need
output(1)wired to a 5xx Respond. Without it, the failure is unhandled: the workflow halts and the caller gets whatever n8n's generic error response is (no controlled status code, no useful body), and the operator only learns about it if a workflow-level error workflow is configured separately. → n8n-error-handling-official non-negotiables and API_WORKFLOWS.md[ ] Error response returns 200. Caller's HTTP client treats it as success; downstream error handling never fires. Always 4xx (caller error) or 5xx (server error). → RESPONSE_SHAPES.md
[ ] Generic 500 for every failure. Validation errors should be 400, auth issues 401/403, conflicts 409, rate limits 429. Caller can't distinguish their bug from your outage. → RESPONSE_SHAPES.md
[ ]
respondWith: 'json'body usingJSON.stringify(...)instead of an object literal (produces double-encoded body). → WEBHOOK_NODES.md
HTTP Request
[ ] Headers set both via
headerParametersand a credential'shttpHeaderAuth. They conflict. → HTTP_NODES.md[ ] Untrusted external API call without
options.timeoutset. Runtime default is 5 minutes; a hung request stalls the workflow. → HTTP_NODES.md
Schedule trigger
[ ] Business-critical schedule with no explicit timezone at the workflow level. DST and instance moves cause timing shifts. → TRIGGER_NODES.md
[ ] Schedule-triggered workflow not idempotent for missed-run scenarios. Restarts or downtime can miss runs. → TRIGGER_NODES.md
NICE TO HAVE
Naming
[ ] Workflow name doesn't follow verb-first pattern (
Send weekly customer reportvs.Customer report sender). → NAMING_CONVENTIONS.md[ ] Untagged sub-workflow (missing
subworkflow, a domain tag, ortool). Tags are the discovery mechanism; an untagged sub-workflow won't surface under anytagsfilter. → NAMING_AND_DISCOVERY.md
Readability
[ ] Canvas over 7 top-level boxes (n8n 2.28+) whose logical steps aren't grouped into node groups. Boxes are the trigger, each group, and each ungrouped node; sub-nodes and stickies don't count. Collapsed groups make the canvas read as steps, not nodes. Skip on older instances, where node groups don't exist. → NODE_GROUPS.md
[ ] Group with no
description, or one written to restate its title. Groups are created collapsed, so title plus description is all a reader gets. Max 145 characters. → NODE_GROUPS.md[ ] An Agent grouped apart from its sub-nodes. Any non-main connection crossing the boundary drops the group; all in or all out. → NODE_GROUPS.md
[ ] Sticky note used to fake a group instead of a node group. Stickies annotate (callouts, TODOs, warnings); they don't group. → SKILL.md "Readability"
[ ] Sticky title re-states what's visible (
Set, If, Set). Title with the callout's point. → SKILL.md "Readability"[ ] Sticky color used for variety instead of meaning. Color should say what happens there (green creates or involves a person, red destructive or irreversible, blue routing, gray documentation), one meaning per color, matching whatever nearby workflows already use. → STICKY_NOTES.md
[ ] Sticky sitting away from, or on top of, what it describes. On create, anchor it with
sticky(content, [nodes])instead of positioning by hand. A note at[0, 0]is the usual tell, though saved JSON can't distinguish an omittedpositionfrom a deliberate one, so check where it should sit rather than how it got there. → STICKY_NOTES.md[ ] Workflow
descriptionis empty, one sentence, or doesn't capture the why. Two sentences: what it does, why it exists. → SKILL.md "Readability"[ ] Code node without a one-line note explaining its purpose. → SKILL.md "Readability"
[ ] Workflow ignores existing house style (sticky palette, naming convention used in nearby workflows). Match what's there. → SKILL.md "Readability"
Sub-workflow conventions
[ ] Sub-workflow without a final
ReturnSet / Edit Fields node shaping the return contract. The legitimate exception to the Set-node antipattern. → n8n-subworkflows-official "Other conventions"[ ] Inputs / outputs not documented in the sub-workflow's
description. Field names, types, purpose. → n8n-subworkflows-official "Sub-workflow inputs and outputs"
Conventions
[ ] Per-execution context (user identity, files, current task) buried in a static system prompt instead of injected via expressions or piecing. Hard to update. → SYSTEM_PROMPT.md "Storing the prompt"
[ ] Generic safety boilerplate language in the system prompt for risks the model already handles. Reinforcement adds tokens without changing behavior. Reserve safety language for specific, named risks. → SYSTEM_PROMPT.md anti-patterns
Reporting findings
When reporting, group by severity, then within severity by domain. For each finding include:
The specific node(s) or section affected.
A one-sentence description of the issue.
The link to the canonical skill ref for the fix.
MUST FIX
Security
- Node `Send Webhook`: bearer token in headerParameters value field. → n8n-credentials-and-security-official
- Node `Lookup user`: SQL string concat with $json.email. → DATABASE_NODES.md
Connections
- Node `Validate input`: `connections.Validate input.main[0]` has only 1 entry, but the workflow's logic expects fan-out to 3 downstreams. → check the SDK code (likely a missed `.add(...)` call)
SHOULD FIX
...A review agent should not auto-fix MUST FIX items without user confirmation: security and connection fixes have blast radius and the user should know what's changing.
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 API key, 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
- Published by
- n8n
- Publisher
- n8n (Official)
- Source
- n8n-io/skills · skills/n8n-workflow-lifecycle-official · commit 27b6b9f
- Licence
- Apache License 2.0 (redistribution allowed)
- LICENSE (added by AutoGineer from LICENSE)
- ATTRIBUTION.md (written by AutoGineer)
- Synced
AutoGineer is not affiliated with or endorsed by n8n.
Download ZIP
The ZIP holds the skill’s files, its licence files and an ATTRIBUTION.md. Download n8n-workflow-lifecycle-official.zip