Skip to content

AI Agent (Anthropic) silently drops tools when systemMessage is large (~50KB) #37840

Description

@augustocgomes

Environment

  • n8n: 2.31.0 (self-hosted, Docker image n8nio/n8n:2.31.0, Alpine 3.24 base, Node.js v24.16.0 inside the container)
  • @n8n/n8n-nodes-langchain: 2.31.0 (bundled)
  • Nodes involved: @n8n/n8n-nodes-langchain.agent (typeVersion 1.7), @n8n/n8n-nodes-langchain.lmChatAnthropic (typeVersion 1.3)
  • Model: claude-sonnet-5 (Anthropic)
  • Tool node type used: n8n-nodes-base.postgresTool (a regular node used as a tool via the "usable as tool" mechanism, not a purpose-built LangChain tool node)

Symptom

With a real production workflow (Twilio WhatsApp assistant), the AI Agent never calls any of its connected tools when a large static systemMessage (~50KB, built from a business prompt template) is used. The model always finishes with stop_reason: "end_turn" (via the AI Agent's own text output — it never even attempts a tool call), narrating an intention to check something ("let me check...") and then answering from its own embedded knowledge/hallucination instead of calling any tool. No error is raised anywhere — the execution completes successfully.

This was fully reproducible and is not intermittent — confirmed across all real executions retained in the database (11 days of history).

What this is NOT (ruled out with direct evidence)

  • Not the Anthropic API's payload validation / size limit. The exact same request body (large system, tools, messages) that fails through the n8n AI Agent was sent directly to https://api.anthropic.com/v1/messages (bypassing n8n's LangChain node and our internal caching proxy entirely, using n8n's httpRequest node with predefinedCredentialType) and it worked correctly (stop_reason: "tool_use"), both as a plain string system and as the array-with-cache_control format our proxy produces.
  • Not a $fromAI() schema bug (tracked separately, see below) — that bug (missing explicit type argument, matching The $fromAI tool with json type is incompatible with standard JSON Schemas, leading to tools being skipped #18588) was found and fixed independently; it does not explain this symptom on its own (see Cause [feature request] Fliter events/actions in realtime #2).
  • Not our internal caching proxy (source read in full — it does a JSON parse/mutate-system-only/stringify pass, no field is dropped; confirmed via direct byte-for-byte reproduction of its output sent straight to Anthropic).
  • Not the number of connected tools. Full elimination matrix below.
  • Not prompt-caching related (cache_control present or absent makes no difference at the API level, confirmed by direct testing both ways).

Elimination matrix (prompt size × number of tools)

All runs below use the same isolated test workflow, same AI Agent/model config, same credential, only systemMessage length and the set of connected tools varied.

systemMessage # tools connected Result
short (~100 bytes) 1 tool called
long (~50KB) 1 tool NOT called
long (~50KB) 4 tool NOT called
long (~50KB) 7 (matches production) tool NOT called
short (~100 bytes) 7 (matches production) tool called

Number of tools has zero measurable effect — only systemMessage length correlates with the failure.

Two distinct, overlapping causes found under "large prompt"

Cause #1 — tool node missing toolDescription (closed, root cause understood)

A postgresTool node with no descriptionType/toolDescription set at all (relying purely on default/auto behavior) fails to be included/used when systemMessage is large. The same node, unmodified, with a short systemMessage, works fine — so an empty description alone is not sufficient to reproduce it; it only manifests combined with a large prompt.

systemMessage tool has toolDescription? Result
short no tool called
short yes tool called
long no tool NOT called
long yes tool called

Direct-to-Anthropic confirmation: a tool schema with no description field at all, combined with the same ~50KB system prompt and cache_control, sent straight to the Anthropic API, still resulted in stop_reason: "tool_use" — the raw API tolerates a description-less tool fine, at any prompt size. So this is n8n-side behavior, not an Anthropic API constraint.

Cause #2 — tool WITH a proper, well-formed toolDescription STILL fails with a large prompt (root cause NOT understood)

Using a different tool (Consultar Preço do Serviço in our production workflow) that has a clear, well-formed toolDescription string, connected alone, with the same ~50KB systemMessage: the tool is still never called. This rules out Cause #1 as the sole explanation — there is a second, distinct failure mode triggered by prompt size alone, independent of whether the tool has a description.

We have not identified the mechanism for Cause #2. It reproduces reliably in our environment but we stopped investigating further at this point to avoid speculation without evidence.

Related but separate bug already fixed on our side (do not conflate)

Independently, we also found and fixed the known $fromAI() issue where omitting the explicit type argument ($fromAI(key, description) instead of $fromAI(key, description, "string")) can produce a malformed JSON Schema and cause a tool to be silently skipped — this matches #18588 and community reports of "type": "None" schemas. This is a real, separate, already-understood bug that we fixed by adding the explicit type to all our $fromAI() calls. It is not the cause of the symptom described in this issue — both Cause #1 and Cause #2 above were reproduced with the type argument already correctly present.

How to reproduce (minimal workflows attached)

Two exportable minimal workflow JSON files are attached in a follow-up comment on this issue (reproduces with a placeholder Postgres/Anthropic credential swapped in):

  • repro_causa1_tool_sem_descricao.json — single postgresTool with no toolDescription, large filler systemMessage. Fails to call the tool. Swap AI Agent.parameters.options.systemMessage to reference {{ $json.systemPromptSmall }} instead of {{ $json.systemPrompt }} to see it work.
  • repro_causa2_tool_com_descricao.json — same setup, but the tool has a proper toolDescription. Still fails with the large prompt (Cause [feature request] Fliter events/actions in realtime #2, mechanism unknown).

Both need an Anthropic credential and a Postgres credential (any reachable Postgres instance works — the query itself is trivial and only exists to give the tool a $fromAI() parameter). Replace the placeholder credential IDs before running.

To trigger: use "Execute workflow" from the n8n editor (Manual Trigger), send message "My name is Fernanda". Two complementary checks confirm whether the tool was genuinely invoked, and both were used throughout this investigation:

  • Per-node signal: in the execution data, check whether the tool node's name appears as a key in resultData.runData, with a metadata.subRun entry and real extracted arguments under data.ai_tool — this confirms the model's tool-use loop actually reached that specific node. This is what the elimination matrix above is based on.
  • Full-payload signal: n8n's execution record captures the exact request body sent to the model verbatim (under the chat model node's inputOverride). A full-text search for the literal substring "tools" across the complete execution JSON shows whether a tools array was present anywhere in that call. This is the method behind this report's central finding — 0 occurrences in every failing execution we inspected, versus present (and accepted) when the same payload was sent directly to api.anthropic.com bypassing n8n. Note toolDescription (the tool node's static config parameter) shows up regardless — that's workflow metadata, not the runtime request, and should not be confused with an actual tools array in the call.

Both checks agreed in every case we tested. The one thing that is not a valid signal is inferring tool use indirectly from a business-level side effect (e.g., "a database row was created, so the tool must have run") — in our production workflow a separate deterministic fallback (unrelated to the AI Agent's own tool-calling) can produce the same side effect from the model's plain text output, independent of whether any tool was actually invoked.

Summary

  • Confirmed: not an Anthropic API limitation (same payload works when sent directly).
  • Confirmed: not proxy/cache-related, not tool-count related, not the $fromAI() type bug (fixed separately).
  • Confirmed root cause for one specific pattern (Cause No json data from any APIs #1: missing toolDescription + large prompt).
  • Confirmed real but unexplained second pattern (Cause [feature request] Fliter events/actions in realtime #2: even a well-described tool fails under the same large-prompt condition) — reported as-is, without speculation on the internal mechanism, for maintainers to investigate further.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    status:in-linearIssue or PR is now in Linearstatus:team-assignedA team has been assigned the issue or PRteam:aiIssue is with the AI team

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions