Repository navigation
fix(gateway): servers sharing a tool name fail the deploy, naming the clash, instead of serving part of their tools - #176
Merged
Conversation
… clash, instead of serving part of their tools Discovery registered each server's tools in turn and raised on the first name it had already registered, so the servers listed before the clash kept their tools, the rest had none, and the deploy passed: the error reached only the gateway's log. Discovery now lists every server first and checks names across them and the gateway's own list_website_urls (get_time keeps its backing-server-wins rule) before registering any proxy, and registers none on a clash or a server that fails to list. The gateway records the clash, reports it as `tool_name_clashes` on GET /state and in /step's 503, and stays up rather than exiting at startup. Once the card is served, both deploy paths read /state and raise DeployRefused naming each tool and its servers; a gateway that can't be read or doesn't report clashes is let through. A chain raises DeployRefused at once instead of trying the next member. A failed deploy that terminates the VM it created now also removes a local one's work folder, which no teardown reaches since nothing records the VM. The composed-universe slow test's multi holds three items servers that all registered list_items; they now prefix it with their environment_name. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
agentenvhub-sdk 0.34.0, which the integration build installs, needs agentenv-framework>=0.9.1319. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
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.
If two servers behind a gateway serve the same tool name, the deploy now fails and names the tool and both servers. Before, it passed, and the env served a partial tool set.
Why
The gateway registered each server's tools in turn and raised on the first name it had already registered. Everything registered before that point stayed registered. Here is a multi of three items servers on main (
items,stock,ledger), each registeringlist_items:items_add_item,list_itemsandstock_add_item, and none of ledger's tools;Duplicate tool name 'list_items': already registered from 'items', cannot register from 'stock', appeared only in the gateway container's log, and every/stepand new MCP session hit it again.An agent on that env sees some servers' tools and not others, and the run fails for reasons that look unrelated.
What
All or nothing. Discovery lists every server's tools first and checks names across all of them, and across the gateway's own
list_website_urls, before it registers any proxy. On a clash it registers nothing. A server that fails to list also leaves nothing registered now, where before the servers listed ahead of it kept their tools.get_timekeeps its rule: a backing server's tool of that name wins, and the clock refuses to arm over it.The gateway reports the clash.
GET /stategainstool_name_clashes, a map from each clashing name to its sources, e.g.{"list_items": ["items", "stock"]}; it is empty when nothing clashes./stepanswers 503 naming the clash. Servers' names don't change during a deploy, so neither does a clash: it is recorded once, and not rediscovered on each request.The deploy refuses it. Once the card is served, both deploy paths (VM and Modal containers) read
/stateand raiseDeployRefused:A gateway whose
/statecan't be read, or that doesn't report clashes (an image built before this change), is let through as before.A chain stops at a refused deploy. Every member would refuse it the same way, so
_runraisesDeployRefusedat once instead of deploying again on the next member.A failed local deploy removes its work folder. When a failed or cancelled deploy terminates the VM it created, a sandbox on this machine now also loses its work folder. The run's teardown removes it for recorded sandboxes, but a deploy that fails is never recorded. The new slow test found this; any local gateway deploy that failed after creating its sandbox left the folder behind.
The protocol README says names must also differ across the servers behind one gateway, and that
{environment_name}keeps them apart.Rollout
The provider check ships with the release. The gateway side reaches remote deploys only once the
defaultgateway env is re-put in dev and prod. Until then, remote deploys behave as today: the old image doesn't report clashes, so the check lets them through. Local bundle runs build the gateway from the installed package, so they get both sides at once.Before the re-put,
env_deploy_tools_probe ... status=missingin the hub and worker logs would show any prod multi that hits a clash today. Such a multi runs on a partial tool set now and will be refused after the re-put. Synthetic servers prefix their tools with their service name, so a clash would take the same server deployed twice under different env names.Tests
Unit tier and the protocol suite: 7,518 passed. New:
list_itemsregisters no tool, and the clash names the tool and both servers; three sources and several names are all named; a clash is raised again without listing the servers again;/statereports it and/stepanswers 503 naming it; with no clash,/statereports{}and every tool is registered; a backinglist_website_urlsclashes with the gateway's own, which stays registered; a backingget_timestill wins over an armed clock with no clash; a server that fails to list leaves no tool of the servers listed before it; a clash at startup is logged once, without a traceback, and the gateway stays up;/statereporting a clash is refused with that message; one reporting none, without the field, or that can't be read lets the deploy through; a container deploy whose gateway reports a clash raisesDeployRefusedthrough a two-member chain, without trying the second, and terminates every container it made; the VM deploy reads/stateat the gateway URL; a failed deploy removes a local VM's work folder, and leaves a remote one's alone;_runstops atDeployRefusedand closes the attempt.Slow tier (
run_bundle_built_envs_test.py):list_itemsfails at its deploy step with the tool and both servers named, and leaves no sandbox work folder;list_itemstoo. That deploy now fails, so those servers prefix it with theirenvironment_name, and the comment saying the verifier can't use MCP is gone.Live check
From this machine, with
agent-env runon two bundles: a multi of two items servers sharinglist_items, and the same multi with the name prefixed per server, plus a verifier that adds and lists an item on each server through the gateway.Chaos
20 minutes of random cases on each of two fresh Linux VMs, running this branch from a clean clone with the local provider: Ubuntu 24.04 on amd64 (t3.large) and Ubuntu 22.04 on arm64 (t4g.large). After every case, the VM had to have no
agent-env-local-*container, network or volume, and no sandbox work folder.list_items(2-way), three sharing it (3-way), a server registeringlist_website_urlsin a multi with a website, a no-clash multi checked through the gateway, the same with one server sleeping 5–25 s before it serves, SIGINT or SIGTERM at a random 2–40 s, and three runs at once.DeployRefused, naming every tool and server.🤖 Generated with Claude Code


Confidence Score: 5/5
The PR appears safe to merge; no new actionable defects were found.Summary
The gateway checks all discovered tool names before registering proxies. It reports clashes through
/stateand/step, and both deploy paths refuse a reported clash.Diagram
%%{init: {'theme': 'neutral'}}%% flowchart TD A[List every backing server's tools] --> B{Names clash?} B -->|No| C[Build all proxies] C --> D[Register tools] B -->|Yes| E[Cache clash; register no backing tools] E --> F[Report clash through /state] F --> G[Deploy raises DeployRefused] G --> H[Close attempt and stop provider chain]Reviews (2) · Last reviewed commit: "Merge main (v0.9.1319) into gateway-dupl..." · Reviewed by Greptile