n8n-io/n8n · error · Error

Tool ${toolCall.toolName} not found

Error message

Tool ${toolCall.toolName} not found

What it means

Thrown when a node is correctly configured for a raw XML/SOAP body (sendBody=true, contentType='raw', rawContentType matching an XML media type) but the body field itself is empty or not a string. Without a body the request would be sent empty, so the validator blocks it.

Source

Thrown at packages/@n8n/agents/src/runtime/loop/agent-runtime.ts:377

		if (!state) {
			throw new StaleResumeError(`No suspended run found for runId: ${this.runId}`);
		}

		const toolCall = state.pendingToolCalls[options.toolCallId];
		if (!toolCall) {
			throw new StaleResumeError(`No tool call found for toolCallId: ${options.toolCallId}`);
		}

		const list = AgentMessageList.deserialize(state.messageList);
		this.context.hydrateDeferredToolsFromList(list);
		await hydrateFileParts(list.messages(), this.config.fileStore, {
			threadId: state.persistence?.threadId,
		});

		const tool = this.context
			.getCurrentTools(state.persistence)
			.find((t) => t.name === toolCall.toolName);
		if (!tool) throw new Error(`Tool ${toolCall.toolName} not found`);

		let resumeData: unknown = data;
		let abortScope: AgentAbortScope | undefined;

		const resumeSchema = toolCall.suspended ? toolCall.resumeSchema : tool.resumeSchema;
		if (!isCancellation(resumeData) && resumeSchema) {
			const parseResult = await parseWithSchema(resumeSchema, data, { stripUnknown: true });
			if (!parseResult.success) {
				throw new Error(`Invalid resume payload: ${parseResult.error}`);
			}
			resumeData = parseResult.data as JSONValue;
		}

		try {
			// Merge persisted execution options with fresh caller options
			const {
				runId: _rid,
				toolCallId: _tcid,

View on GitHub (pinned to 5ac6606e81)

Solutions

  1. Put the XML envelope string (or an expression like expr('{{ $json.soapBody }}')) into the body field.
  2. Confirm rawContentType stays an XML media type and contentType stays 'raw'.
  3. Remove any specifyBody/jsonBody/bodyParameters so only the raw body is used.

Example fix

// before
httpRequest({
  name: 'SOAP Call',
  sendBody: true,
  contentType: 'raw',
  rawContentType: 'text/xml',
  body: '',
});

// after
httpRequest({
  name: 'SOAP Call',
  sendBody: true,
  contentType: 'raw',
  rawContentType: 'text/xml',
  body: expr('{{ $json.soapEnvelope }}'),
});
Defensive patterns

Strategy: validation

Validate before calling

const XML_MEDIA = /\b(?:text|application)\/(?:[\w.+-]*\+)?xml\b/i;

function isMissingBody(value: unknown): boolean {
  return typeof value !== 'string' || value.trim() === '';
}

// before building:
if (params.sendBody === true && params.contentType === 'raw' && XML_MEDIA.test(String(params.rawContentType ?? '')) && isMissingBody(params.body)) {
  throw new Error('Raw XML body configured but body is empty; set body to the envelope or an expression.');
}

Prevention

When it happens

Trigger: params.sendBody === true AND params.contentType === 'raw' AND rawContentType matches the XML media pattern (text/xml, application/xml, application/...+xml) AND isMissingBody(params.body) — i.e. body is not a string or is whitespace-only.

Common situations: Configuring the raw content type but forgetting to fill body; referencing a field that resolves to empty at build time; an AI builder sets up the envelope scaffolding but leaves body blank.

Related errors


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