n8n-io/n8n · error · NodeOperationError

Wrong output type returned

Error message

Wrong output type returned

What it means

Thrown by WorkflowToolService.handleToolResponse (v2) when the sub-workflow response is not a number, not an INodeExecutionData[], not an object, and not a string — i.e. it is a boolean, function, symbol, bigint, or similar. The tool must serialize the response to a string for the agent, and it has no serializer for these types.

Source

Thrown at packages/@n8n/nodes-langchain/nodes/tools/ToolWorkflow/v2/utils/WorkflowToolService.ts:245

	private handleToolResponse(response: unknown): string {
		if (typeof response === 'number') {
			return response.toString();
		}

		if (isNodeExecutionData(response)) {
			return JSON.stringify(
				response.map((item) => item.json),
				null,
				2,
			);
		}

		if (isObject(response)) {
			return JSON.stringify(response, null, 2);
		}

		if (typeof response !== 'string') {
			throw new NodeOperationError(this.baseContext.getNode(), 'Wrong output type returned', {
				description: `The response property should be a string, but it is an ${typeof response}`,
			});
		}

		return response;
	}

	/**
	 * Executes specified sub-workflow with provided inputs
	 */
	private async executeSubWorkflow(
		context: ISupplyDataFunctions | IExecuteFunctions,
		workflowInfo: IExecuteWorkflowInfo,
		items: INodeExecutionData[],
		workflowProxy: IWorkflowDataProxyData,
		runManager?: CallbackManagerForToolRun,
	): Promise<{ response: IDataObject | INodeExecutionData[]; subExecutionId: string }> {
		let receivedData: ExecuteWorkflowData;

View on GitHub (pinned to 5ac6606e81)

Solutions

  1. In the sub-workflow, convert the final value to a string or object before the terminal node (e.g. a Set node with String(value)).
  2. Ensure the terminal node always emits a plain JSON-shaped item (string/number/object/array).
  3. If returning all items, make sure the output is an array of { json: ... } items.

Example fix

// before: sub-workflow returns { json: { result: true } } and only `true` is read → boolean → throws
// after: wrap it
{ json: { response: 'Yes' } }
// or stringify in a Code node: return [{ json: { response: String(items[0].json.flag) } }];
Defensive patterns

Strategy: type-guard

Validate before calling

if (typeof response !== 'string' && typeof response !== 'number' && !isObject(response) && !isNodeExecutionData(response)) {
  response = String(response); // coerce before handleToolResponse
}

Type guard

function isToolSerializable(v: unknown): v is string | number | object | INodeExecutionData[] {
  return ['string','number','object'].includes(typeof v) && v !== null;
}

Try / catch

try { return this.handleToolResponse(response); }
catch (e) { return String(response); }

Prevention

When it happens

Trigger: A sub-workflow whose terminal node returns a boolean or bigint as the json value; returnAllItems=true with a non-array, non-object payload; an edge case where the response is a function/symbol from a misbehaving custom node.

Common situations: Sub-workflows returning a bare boolean (true/false) as the answer; numeric/bigint counters; exotic types produced by Code nodes that return non-JSON values.

Related errors


AI-assisted analysis of n8n-io/n8n@5ac6606e81 (2026-08-12). Data as JSON: /api/errors/38e0351b03ef4f78. Report an issue: GitHub.