{"record":{"id":"2660b523351ec8a1","repo":"alibaba/nacos","slug":"duplicate-runtime-endpoint-endpointkey","errorCode":null,"errorMessage":"Duplicate runtime Endpoint: {endpointKey}","messagePattern":"Duplicate runtime Endpoint: (.+?)","errorType":"validation","errorClass":"IllegalArgumentException","httpStatus":null,"severity":"error","filePath":"api/src/main/java/com/alibaba/nacos/api/ai/utils/AgentModelValidator.java","lineNumber":559,"sourceCode":"        for (Endpoint endpoint : endpoints) {\n            validateEndpoint(endpoint);\n            EndpointNaturalKey key = EndpointNaturalKey.of(namespaceId, agentName, protocol,\n                endpoint);\n            if (!endpointKeys.add(key)) {\n                throw new IllegalArgumentException(\"Duplicate declared Endpoint: \" + key);\n            }\n        }\n    }\n    \n    private static void validateRuntimeEndpointSnapshotItem(RuntimeEndpointSnapshot snapshot,\n        AgentVersion selectedVersion, RuntimeEndpointSnapshotItem item,\n        Set<EndpointNaturalKey> endpointKeys) {\n        requireNonNull(item, \"runtimeEndpointSnapshot item\");\n        validateEndpoint(item.getEndpoint());\n        EndpointNaturalKey endpointKey = EndpointNaturalKey.of(snapshot.getNamespaceId(),\n            snapshot.getAgentName(), snapshot.getProtocol(), item.getEndpoint());\n        if (!endpointKeys.add(endpointKey)) {\n            throw new IllegalArgumentException(\"Duplicate runtime Endpoint: \" + endpointKey);\n        }\n        \n        List<RuntimeVersionBinding> bindings = item.getBindings();\n        requireNonNull(bindings, \"runtimeEndpointSnapshot.bindings\");\n        if (bindings.isEmpty()) {\n            throw new IllegalArgumentException(\"Runtime Endpoint bindings must not be empty\");\n        }\n        Set<String> bindingKeys = new HashSet<String>();\n        for (RuntimeVersionBinding binding : bindings) {\n            validateRuntimeVersionBinding(binding, selectedVersion, bindingKeys);\n        }\n        requireNonNull(item.getState(), \"runtime Endpoint state\");\n        requireNonNull(item.getEnabled(), \"runtime Endpoint enabled\");\n        requireNonNull(item.getHealthy(), \"runtime Endpoint healthy\");\n        validateRuntimeEndpointState(item);\n        validateEpochMillis(item.getLastUpdatedTime(), \"lastUpdatedTime\");\n    }\n    ","sourceCodeStart":541,"sourceCodeEnd":577,"githubUrl":"https://github.com/alibaba/nacos/blob/9b989acdf181d00898f2e8839257bb2b2a3cefe3/api/src/main/java/com/alibaba/nacos/api/ai/utils/AgentModelValidator.java#L541-L577","documentation":"Thrown while validating a runtime Endpoint snapshot (RuntimeEndpointSnapshot) when two snapshot items resolve to the same EndpointNaturalKey across the whole snapshot. Unlike declared endpoints (per-CallInterface), runtime uniqueness is enforced across all items of one snapshot. The key is (namespaceId, agentName, protocol, normalizedHost, effectivePort, transport), so URIs that differ only cosmetically collide.","triggerScenarios":"validateRuntimeEndpointSnapshot iterates snapshot.getItems(), passing one shared endpointKeys set into validateRuntimeEndpointSnapshotItem, which calls EndpointNaturalKey.of(snapshot.getNamespaceId(), snapshot.getAgentName(), snapshot.getProtocol(), item.getEndpoint()). The second item with an already-seen key triggers this. Reached via AgentModelValidator.validateRuntimeEndpointSnapshot, typically invoked by the runtime management / RAD (Remote Agent Discovery) push path.","commonSituations":"A runtime agent reporting the same backend address twice with different health/bindings/state (those do not affect identity); an aggregator merging snapshots from two sub-agents without dedup; URI case/default-port collisions such as 'https://H' and 'https://h:443'; switching a host between IPv4 and a hostname that resolve to the same effective address but you intended distinct entries.","solutions":["Dedup snapshot items by the canonical (host, port, transport) tuple; the message prints the colliding key as namespaceId/agentName/protocol/host:port/transport.","Merge health, bindings, enabled, and state into a single RuntimeEndpointSnapshotItem for that address rather than emitting two.","If you need the same host on two distinct transports (e.g. grpc vs http), ensure the transport field differs — that is part of the natural key.","Pre-compute EndpointNaturalKey.of(...) for each item client-side to detect collisions before push."],"exampleFix":"// before\nitemA.setEndpoint(uri(\"https://svc:443\")); itemA.setState(AVAILABLE);\nitemB.setEndpoint(uri(\"https://svc:443\")); itemB.setState(DISABLED); // duplicate key\n// after\nitemA.setEndpoint(uri(\"https://svc:443\")); // single item, pick one authoritative state","handlingStrategy":"validation","validationCode":"import com.alibaba.nacos.api.ai.utils.EndpointNaturalKey;\nimport java.util.HashSet;\nimport java.util.Set;\n\nSet<EndpointNaturalKey> seen = new HashSet<>();\nfor (RuntimeEndpointSnapshotItem it : snapshot.getItems()) {\n    EndpointNaturalKey k = EndpointNaturalKey.of(\n        snapshot.getNamespaceId(), snapshot.getAgentName(),\n        snapshot.getProtocol(), it.getEndpoint());\n    if (!seen.add(k)) {\n        throw new IllegalStateException(\"duplicate runtime endpoint: \" + k);\n    }\n}","typeGuard":"static boolean noRuntimeDuplicates(RuntimeEndpointSnapshot snap) {\n    Set<EndpointNaturalKey> s = new HashSet<>();\n    for (RuntimeEndpointSnapshotItem it : snap.getItems()) {\n        if (!s.add(EndpointNaturalKey.of(snap.getNamespaceId(), snap.getAgentName(),\n                snap.getProtocol(), it.getEndpoint()))) return false;\n    }\n    return true;\n}","tryCatchPattern":"try {\n    AgentModelValidator.validateRuntimeEndpointSnapshot(snapshot);\n} catch (IllegalArgumentException e) {\n    if (e.getMessage().startsWith(\"Duplicate runtime Endpoint\")) {\n        // merge the two items' health/bindings/state into one\n    } else throw e;\n}","preventionTips":["Uniqueness spans the whole snapshot, not per item.","Merge health/state/bindings for the same address instead of emitting two items.","Same host:port but different transport IS allowed — set transport explicitly."],"tags":["ai-agent","validation","duplicate","runtime-snapshot","endpoints"],"backgroundTag":null,"analyzedSha":"9b989acdf181d00898f2e8839257bb2b2a3cefe3","analyzedAt":"2026-08-14T07:17:31.569Z","schemaVersion":2},"datasetVersion":"2026-08-14T10:17:34.591Z"}