When an n8n sub-workflow change breaks the workflows that call it
An n8n sub-workflow is shared code: when you publish a change to it, every workflow that calls it can change behavior, yet none of those parent workflows shows an edit. To ship a sub-workflow change safely, find every caller first, then run each one against your unpublished draft before you publish it.
Why doesn’t the parent workflow show a sub-workflow change?
Because the parent stores only a reference. The Execute Sub-workflow node names the sub-workflow by its workflow ID, so when the sub-workflow changes, the parent’s definition stays byte-for-byte the same. The change is visible in the sub-workflow’s own history; it is invisible from the parent, and nothing in n8n connects the two.
n8n’s documentation on workflow reviews says so directly: a review covers one workflow’s own nodes and connections, and each sub-workflow it calls is its own workflow, with its own review if any. AI agents add a second kind of caller: the Call n8n Workflow Tool node runs a sub-workflow too, and it is easy to forget when you list who depends on one.
When does a sub-workflow change reach the workflows that call it?
When you publish the sub-workflow. On n8n 2.x a parent’s production executions call the sub-workflow’s published version, so an edited but unpublished draft changes nothing for clients yet, and publishing it changes every caller at once. A manual run of the parent from the editor calls the sub-workflow’s latest draft instead, which is what makes testing before you publish possible.
We checked this on a test instance of n8n 2.44.6, because n8n’s documentation does not spell it out. The same test showed two more things worth knowing. Unpublishing a sub-workflow makes its parents’ production runs fail. And updating a published sub-workflow through n8n’s public API publishes it immediately, so a script or CI job that pushes a sub-workflow is a deploy to every caller.
Which sub-workflow changes break the parent?
Any change to what the sub-workflow returns or expects. The parent receives the output of the sub-workflow’s last node, so renaming a field, changing how many items come back, or ending on a different node changes the parent’s data, often with no error, because an expression that reads a missing field resolves to nothing.
- Output shape. A renamed or removed field leaves
{{ $json.total }}in the parent empty, not failing. - Item count. Returning one summary item instead of one per input, or no items at all, changes how often everything downstream runs. Zero items quietly stops the branch.
- Inputs. When the sub-workflow’s trigger defines its inputs field by field, adding or renaming one changes what every caller has to send.
- Errors. n8n’s documentation notes that a parent cannot trigger a sub-workflow that contains errors.
- Not waiting. With Wait for Sub-Workflow Completion turned off, the parent carries on without the result, so a failure in the sub-workflow never appears in the parent’s execution.
Upgrades belong on this list too. In n8n 2.0, a parent that waits on a sub-workflow which pauses (a Wait node over 65 seconds, a webhook, a form, a human approval) began receiving the sub-workflow’s real output; in 1.x it received the sub-workflow’s input back. A parent built around the old behavior changed without anyone editing it.
How do I find every workflow that calls a sub-workflow?
Search every workflow definition for the sub-workflow’s ID. Export all workflows with n8n export:workflow --all and search the output for the ID, which appears in Execute Sub-workflow nodes and in the Call n8n Workflow Tool nodes AI agents use. Then limit which workflows may call it, in the sub-workflow’s settings under “This workflow can be called by”, so the list cannot quietly grow.
n8n export:workflow --all --separate --output=workflows/
grep -l "YOUR_SUB_WORKFLOW_ID" workflows/*.json
That lists one file per workflow that mentions the ID; ignore the sub-workflow’s own file, which names itself. A text search has two blind spots worth knowing. A caller that sets the ID with an expression will not match the literal ID, and a caller that embeds the sub-workflow as JSON in the node is not calling the shared workflow at all, so your change will not reach it.
How do I test a sub-workflow change before publishing it?
First test the sub-workflow on its own, then test its callers where nothing they run can reach a client. A manual run of a parent calls your unpublished draft, which is what makes it a real test, but it also runs every node in that draft for real, including ones that send, charge or update, and pins inside the sub-workflow do not stop them when a parent calls it.
- The sub-workflow alone. Open one of its own recent executions, use Copy to editor so it runs on an input a caller really sent, pin its nodes that reach the outside world, and compare its output with that execution’s. Pins do apply when you run the sub-workflow directly.
- Its callers, safely. Run each important parent manually against the draft only where the draft cannot do harm: on a development instance with test credentials, or when the sub-workflow sends nothing outside n8n. Copy to editor on a recent execution of the parent gives it a real input; compare node by node.
- Do not disable nodes in the draft to make the test safe. The draft is what you are about to publish, and a disabled node would ship disabled.
- Publish, then watch the next production run of each caller.
What does Preflight catch here, and what doesn’t it?
Not this change, yet. When Preflight replays a parent it answers every sub-workflow call from the recording, or switches the call off when there is none, and calls made through an AI agent’s workflow tool are always switched off, so a green replay of a parent says nothing about a change to the sub-workflow it calls. The replay report names each of those nodes as not vouched for rather than counting it as a pass.
Until Preflight can follow a call into the sub-workflow, the manual parent run above is the test to use.
The wider method: how to test n8n workflow changes before production. Why a clean definition diff is not enough: definition diffs vs behavior testing.