{"record":{"id":"943d471c7a596123","repo":"alibaba/nacos","slug":"duplicate-runtime-endpoint","errorCode":null,"errorMessage":"Duplicate Runtime Endpoint: {}","messagePattern":"Duplicate Runtime Endpoint: (.+?)","errorType":"validation","errorClass":"IllegalArgumentException","httpStatus":null,"severity":"error","filePath":"ai/src/main/java/com/alibaba/nacos/ai/service/agent/fingerprint/RuntimeEndpointRevision.java","lineNumber":103,"sourceCode":"            throw new IllegalArgumentException(\"Runtime Endpoint projection must not be null\");\n        }\n        if (endpoints.size() > MAX_ENDPOINTS) {\n            throw new IllegalArgumentException(\n                \"Runtime Endpoint projection exceeds \" + MAX_ENDPOINTS + \" items\");\n        }\n        Map<EndpointNaturalKey, AgentDiscoveryEndpoint> canonicalEndpoints =\n            new TreeMap<EndpointNaturalKey, AgentDiscoveryEndpoint>();\n        for (AgentDiscoveryEndpoint endpoint : endpoints) {\n            Endpoint canonicalEndpoint = EndpointCanonicalizer.canonicalize(endpoint);\n            AgentDiscoveryEndpoint canonical = copyEndpoint(canonicalEndpoint);\n            if (canonical.getHealthy() == null) {\n                throw new IllegalArgumentException(\"Runtime Endpoint healthy must not be null\");\n            }\n            canonical.setBindings(canonicalBindings(endpoint.getBindings()));\n            EndpointNaturalKey key = EndpointNaturalKey.of(namespaceId, agentName, protocol,\n                canonical);\n            if (canonicalEndpoints.put(key, canonical) != null) {\n                throw new IllegalArgumentException(\"Duplicate Runtime Endpoint: \" + key);\n            }\n        }\n        return frame(canonicalEndpoints);\n    }\n    \n    private static byte[] frame(Map<EndpointNaturalKey, AgentDiscoveryEndpoint> endpoints) {\n        ByteArrayOutputStream buffer = new ByteArrayOutputStream();\n        try (DataOutputStream output = new DataOutputStream(buffer)) {\n            output.writeInt(endpoints.size());\n            for (AgentDiscoveryEndpoint endpoint : endpoints.values()) {\n                writeUtf8(output, endpoint.getUri());\n                writeUtf8(output, endpoint.getTransport());\n                output.writeInt(endpoint.getPriority());\n                double weight = endpoint.getWeight() == 0D ? 0D : endpoint.getWeight();\n                output.writeLong(Double.doubleToLongBits(weight));\n                writeMetadata(output, endpoint.getMetadata());\n                output.writeBoolean(endpoint.getHealthy());\n                writeBindings(output, endpoint.getBindings());","sourceCodeStart":85,"sourceCodeEnd":121,"githubUrl":"https://github.com/alibaba/nacos/blob/9b989acdf181d00898f2e8839257bb2b2a3cefe3/ai/src/main/java/com/alibaba/nacos/ai/service/agent/fingerprint/RuntimeEndpointRevision.java#L85-L121","documentation":"Within a single runtime Endpoint projection, two endpoints cannot share the same natural key (namespaceId + agentName + protocol + canonical host:port + transport). This guard in revisionBytes detects duplicates after canonicalization and rejects them, ensuring the revision frame is deterministic and unambiguous.","triggerScenarios":"Calling RuntimeEndpointRevision.compute with an endpoints list containing two AgentDiscoveryEndpoints whose EndpointNaturalKey is identical after URI canonicalization (e.g., same host, port, and transport). Two endpoints that differ only in priority, weight, or metadata but share host:port/transport collide.","commonSituations":"A load balancer reports the same backend instance twice with different metadata. An auto-registration loop registers an endpoint that is already present. Two agents behind the same host:port with the same transport token but different URIs that canonicalize to the same host:port.","solutions":["Deduplicate endpoints by natural key (host, port, transport) before computing the revision.","If two instances share the same host:port, use a different transport token to distinguish them.","Merge duplicate endpoints by combining their metadata, priority, and weight instead of keeping both."],"exampleFix":"// before — two endpoints on the same host:port/transport\nendpoints.add(ep1); // grpc://10.0.0.1:8080\nendpoints.add(ep2); // grpc://10.0.0.1:8080 (same natural key)\nRuntimeEndpointRevision.compute(ns, name, proto, endpoints); // throws\n\n// after — deduplicate by natural key\nMap<EndpointNaturalKey, AgentDiscoveryEndpoint> deduped = new LinkedHashMap<>();\nfor (AgentDiscoveryEndpoint ep : endpoints) {\n    EndpointNaturalKey key = EndpointNaturalKey.of(ns, name, proto, ep);\n    deduped.putIfAbsent(key, ep);\n}\nRuntimeEndpointRevision.compute(ns, name, proto, new ArrayList<>(deduped.values()));","handlingStrategy":"validation","validationCode":"Set<EndpointNaturalKey> seen = new HashSet<>();\nfor (AgentDiscoveryEndpoint ep : endpoints) {\n    if (!seen.add(EndpointNaturalKey.of(ns, name, proto, ep))) {\n        throw new IllegalArgumentException(\"Duplicate endpoint: \" + ep.getUri());\n    }\n}","typeGuard":"boolean noDuplicateKeys(String ns, String name, String proto, List<AgentDiscoveryEndpoint> endpoints) {\n    Set<EndpointNaturalKey> seen = new HashSet<>();\n    for (AgentDiscoveryEndpoint ep : endpoints) {\n        if (!seen.add(EndpointNaturalKey.of(ns, name, proto, ep))) return false;\n    }\n    return true;\n}","tryCatchPattern":null,"preventionTips":["Deduplicate endpoints by natural key (host:port + transport) before revision computation.","Use distinct transport tokens for instances sharing the same host:port.","Merge metadata for duplicate endpoints instead of submitting both."],"tags":["agent","ai-registry","rad-protocol","runtime-endpoint","duplicate"],"backgroundTag":null,"analyzedSha":"9b989acdf181d00898f2e8839257bb2b2a3cefe3","analyzedAt":"2026-08-14T07:17:31.569Z","schemaVersion":2},"datasetVersion":"2026-08-14T10:17:34.591Z"}