n8n-io/n8n · error · ConnectionError
INVALID_CONNECTION
INVALID_CONNECTION
Error message
Invalid connection
What it means
Thrown by the connect_nodes tool after validateConnection() (utils/connection.utils) returns { valid: false }. The tool already inferred a connection type and applied its swap heuristic, but the final source/target/type topology is still not permitted by the node types' declared inputs and outputs. validation.error carries the specific reason when available, otherwise the generic 'Invalid connection' fallback is used.
Solutions
- Inspect details.connectionType and details.sourceNode/targetNode, then check each node type's inputs/outputs to find a genuinely compatible pair.
- Try the inverse direction explicitly (swap sourceNodeId and targetNodeId) in case inference picked the wrong orientation.
- Insert a bridging node (No-Op, Set, or Merge) whose inputs/outputs match both ends.
Example fix
// before: two triggers cannot connect
await connect_nodes.invoke({ sourceNodeId: triggerA, targetNodeId: triggerB });
// after: route through a Set node that accepts main input and emits main output
await connect_nodes.invoke({ sourceNodeId: triggerA, targetNodeId: setNode });
await connect_nodes.invoke({ sourceNodeId: setNode, targetNodeId: triggerB }); Defensive patterns
Strategy: validation
Validate before calling
// Before calling connect_nodes, confirm the two node types share a compatible
// connection type by inspecting their INodeTypeDescription outputs/inputs.
function findCompatibleConnectionType(src: INodeTypeDescription, tgt: INodeTypeDescription): string | null {
const outTypes = new Set((src.outputs.main?.[0] ?? []).flatMap((o) => o));
for (const input of tgt.inputs.main ?? []) {
if (input.some((t) => outTypes.has(t))) return 'main';
}
// repeat for ai_* keys present on both sides
return null;
}
const type = findCompatibleConnectionType(srcType, tgtType);
if (!type) { /* surface guidance to caller instead of invoking */ } Type guard
function isToolError(v: unknown): v is { message: string; code: string; details?: unknown } {
return typeof v === 'object' && v !== null && 'code' in v && 'message' in v;
} Try / catch
// connect_nodes returns a structured error response rather than throwing, so
// inspect the returned object's code field rather than try/catch.
const res = await connect_nodes.invoke(input);
if (isToolError(res) && res.code === 'INVALID_CONNECTION') {
// read res.details.connectionType / sourceNode / targetNode, pick a different pair
} Prevention
- Maintain a registry of node type IO signatures and pre-validate pairs before invoking connect_nodes.
- Prefer connecting through well-known bridge nodes (No-Op, Set) when type compatibility is uncertain.
- Log the inferred connectionType on success to build a compatibility map over time.
When it happens
Trigger: Calling connect_nodes with two node IDs whose node types share no compatible output/input pair after type inference and swap; e.g. two trigger nodes, or two main nodes where a sub-node/main-node relationship is required.
Common situations: Trying to chain two trigger nodes (both only produce, neither consumes main); connecting a node whose only output type is ai_memory to a target with no ai_memory input; connecting nodes whose versions changed their IO declarations.
Related errors
- CONNECTION_TYPE_NOT_FOUND
- SELF_CONNECTION
- Cannot pass an OutputSelector to .
- CONNECTION_NOT_FOUND
- () requires , but received .
AI-assisted analysis of n8n-io/n8n@5ac6606e81 (2026-08-12).
Data as JSON: /api/errors/2f9f79d71956a0bd.
Report an issue: GitHub.
Appendix: source
Thrown at packages/@n8n/ai-workflow-builder.ee/src/tools/connect-nodes.tool.ts:214
`Connecting ${matchedSourceNode.name} to ${matchedTargetNode.name}...`,
);
// Validate connection and check if nodes need to be swapped
const validation = validateConnection(
matchedSourceNode,
matchedTargetNode,
connectionType,
nodeTypes,
);
if (!validation.valid) {
const connectionError = new ConnectionError(validation.error ?? 'Invalid connection', {
fromNodeId: matchedSourceNode.id,
toNodeId: matchedTargetNode.id,
});
const error = {
message: connectionError.message,
code: 'INVALID_CONNECTION',
details: {
sourceNode: matchedSourceNode.name,
targetNode: matchedTargetNode.name,
connectionType,
},
};
reporter.error(error);
return createErrorResponse(config, error);
}
// Use potentially swapped nodes
const actualSourceNode = validation.swappedSource ?? matchedSourceNode;
const actualTargetNode = validation.swappedTarget ?? matchedTargetNode;
// Track if nodes were swapped either during inference or validation
const swapped = inferredSwap || !!validation.shouldSwap;
// Create only the new connection (not the full connections object)
// This is important for parallel execution - each tool only returns its own connectionView on GitHub (pinned to 5ac6606e81)