fix(agent): dedupe duplicate tool names with once-per-streak warning#3409
Merged
Conversation
Agent.collectTools appended tools from every toolset verbatim, so two
toolsets emitting the same tool name (same built-in type twice, two
unnamed MCP servers, an MCP tool shadowing a built-in, or a collision
introduced by a ToolListChanged notification) produced a duplicate in
the request and Anthropic rejected it with HTTP 400 'tools: Tool names
must be unique.' On tolerant providers the collision was already broken
too: the dispatcher resolves calls through a name-keyed map, so only
one of the colliding tools was ever reachable.
Deduplicate during collection, keeping the tool from the first toolset
in configuration order, which the MCP docs already promise. Deduping at
collection time (instead of after flattening) keeps the toolset identity
so the warning can attribute both origins.
Collisions are surfaced with once-per-streak semantics mirroring the
existing start/list failure guards: a persistent collision warns once,
a collision that disappears resets the streak, and a new collision is
reported as fresh. The user-facing warning names the kept and ignored
toolsets and points at the config remedies ('name:' prefix on MCP
toolsets or the 'tools:' filter).
Fixes #2251
Signed-off-by: Sayt-0 <louis-dalmorocompta@docker.com>
f4e1571 to
ab0e5b1
Compare
dgageot
approved these changes
Jul 2, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What
Deduplicate tool names in
Agent.collectTools(): the first toolset in configuration order wins, and each collision is surfaced to the user once per streak with a warning naming both toolsets involved and the config remedies.Fixes #2251.
Why
When several toolsets emit the same tool name, the duplicate reaches the provider and Anthropic rejects the request:
Collisions happen in real configs:
filesystem);fetch);ToolListChangednotification.#1969 fixed only the multi-LSP case (multiplexer) and 3528ab2 the multi-RAG case; the general problem remained.
How
tools.DescribeToolSetis still in scope, so the warning attributes both the kept and the ignored origin.docs/tools/mcp/index.md, Toolset order matters callout) but the code never implemented.slog.Debug), a resolved collision resets the streak, a new or reappearing one is reported as fresh.name:on the MCP toolset, or itstools:filter.Two earlier attempts (#2278, #3122) were closed after review. This design addresses each objection raised there:
fetchvs built-infetch)pkg/runtime/toolexec/dispatcher.go), so one tool already silently shadows the other. This PR makes the shadowing deterministic and loudly reported instead of arbitrary and silent.name:mechanism.collectToolsruns every turn but a persistent collision warns only once.Tests
TestAgentTools: new table case — duplicate names across toolsets collapse to a unique set, one warning per dropped duplicate.TestAgentToolsDedupeKeepsFirstInOrder: first occurrence wins, first-seen order preserved.TestAgentToolsCollisionWarningIdentifiesOrigins: warning names both toolsets and thename:remedy.TestAgentToolsCollisionWarningOncePerStreak: warns once for a persistent collision, re-warns only for new or reappearing collisions across turns.Verification
golangci-lint run pkg/agent/...andgo run ./lint .: no issues.go test ./...: passes except pre-existing failures inpkg/tools/builtin/fetchandpkg/tools/builtin/openapi(local network proxy answers 405 on non-public addresses; they fail identically onmain).Out of scope
skillSubSessionToolsinpkg/runtime/loop.go) appends extra toolset tools afterAgent.Tools()and could theoretically reintroduce a duplicate; a shared dedup helper could cover it as a follow-up.