n8n-io/n8n · error · Error

Tool message is missing a tool-result content block

Error message

Tool message is missing a tool-result content block

What it means

In toLcMessage's `tool` role branch, a tool message is required to carry at least one tool-result content block (found via isN8nToolResultBlock). The Langchain ToolMessage needs a tool_call_id and content from that result; a tool message with only text/other blocks has no result to attach, so the converter rejects it rather than fabricating an empty ToolMessage.

Source

Thrown at packages/@n8n/ai-utilities/src/converters/message.ts:356

				.filter(isN8nToolCallBlock)
				.map((c) => ({
					type: 'tool_call',
					id: c.toolCallId,
					name: c.toolName,
					args: jsonParse<Record<string, unknown>>(c.input, { fallbackValue: {} }),
				}));
			const nonToolContent = lcContent.filter((c) => c.type !== 'tool_call');
			return new LangchainMessages.AIMessage({
				content: nonToolContent,
				id: message.id,
				name: message.name,
				tool_calls: toolCalls.length > 0 ? toolCalls : undefined,
			});
		}
		case 'tool': {
			const toolResult = message.content.find(isN8nToolResultBlock);
			if (!toolResult) {
				throw new Error('Tool message is missing a tool-result content block');
			}
			const content =
				typeof toolResult.result === 'string'
					? toolResult.result
					: JSON.stringify(toolResult.result);
			return new LangchainMessages.ToolMessage({
				content,
				tool_call_id: toolResult.toolCallId,
				name: message.name,
				status: toolResult.isError ? 'error' : 'success',
			});
		}
		default:
			return new LangchainMessages.HumanMessage({
				content: lcContent,
				id: message.id,
				name: message.name,
			});

View on GitHub (pinned to 5ac6606e81)

Solutions

  1. Ensure every role:'tool' message includes a tool-result content block (isN8nToolResultBlock) with toolCallId and result.
  2. When trimming conversation history, drop whole tool messages rather than stripping their result blocks.
  3. If the result is genuinely missing, convert the message to role:'user' with text instead.

Example fix

// before
{ role: 'tool', content: [{ type: 'text', text: 'done' }] }

// after
{ role: 'tool', content: [{ type: 'tool_result', toolCallId: 'call_1', result: 'done' }] }
Defensive patterns

Strategy: validation

Validate before calling

import { isN8nToolResultBlock } from '@n8n/ai-utilities';
function hasToolResult(content: unknown[]): boolean {
  return content.some(isN8nToolResultBlock);
}
if (msg.role === 'tool' && !hasToolResult(msg.content)) {
  throw new Error('tool message must include a tool-result block');
}

Type guard

function isToolMessageWithResult(m: { role: string; content: unknown[] }): boolean {
  return m.role !== 'tool' || m.content.some(isN8nToolResultBlock);
}

Prevention

When it happens

Trigger: Constructing an n8n message with role:'tool' whose content array contains no tool-result block — e.g. only a text block, or content was stripped/filtered before conversion; a tool-reply pipeline that lost the result block in transit.

Common situations: A message-merging/trimming routine removed the tool-result block but kept role:'tool'; an LLM/provider adapter emitted a tool message with text only; truncation logic dropped tool blocks by type.

Related errors


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