{"record":{"id":"ce54abaee972ddeb","repo":"alibaba/nacos","slug":"duplicate-declared-endpoint-key","errorCode":null,"errorMessage":"Duplicate declared Endpoint: {key}","messagePattern":"Duplicate declared Endpoint: (.+?)","errorType":"validation","errorClass":"IllegalArgumentException","httpStatus":null,"severity":"error","filePath":"api/src/main/java/com/alibaba/nacos/api/ai/utils/AgentModelValidator.java","lineNumber":546,"sourceCode":"        }\n    }\n    \n    private static void validateDeclaredEndpoints(String namespaceId, String agentName,\n        String protocol, List<Endpoint> endpoints) {\n        if (endpoints == null) {\n            return;\n        }\n        if (endpoints.size() > MAX_DECLARED_ENDPOINTS) {\n            throw new IllegalArgumentException(\n                \"declaredEndpoints exceeds \" + MAX_DECLARED_ENDPOINTS + \" items\");\n        }\n        Set<EndpointNaturalKey> endpointKeys = new HashSet<EndpointNaturalKey>();\n        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()) {","sourceCodeStart":528,"sourceCodeEnd":564,"githubUrl":"https://github.com/alibaba/nacos/blob/9b989acdf181d00898f2e8839257bb2b2a3cefe3/api/src/main/java/com/alibaba/nacos/api/ai/utils/AgentModelValidator.java#L528-L564","documentation":"Thrown when two entries in a CallInterface's declaredEndpoints share the same EndpointNaturalKey. The natural key is the tuple (namespaceId, agentName, protocol, normalizedHost, effectivePort, transport) computed by EndpointNaturalKey.of after URI canonicalization (lowercased scheme/host, inferred default port, IPv6 normalization). Declared endpoints must be address-distinct within one protocol group, so this is a hard uniqueness constraint, not a warning.","triggerScenarios":"validateDeclaredEndpoints iterates the declaredEndpoints list, builds an EndpointNaturalKey per entry via EndpointCanonicalizer, and rejects the second entry whose key was already added to the set. Triggered by Agent publish/update through validateAgent, and by any direct call to AgentModelValidator.validateAgent / validateAgentSummary.","commonSituations":"Two endpoints that look different in the raw URI but canonicalize to the same host:port:transport (e.g. 'http://h:80' vs 'http://h', or 'http://H' vs 'http://h'); a copy-paste that re-lists the same address with different priority/weight/metadata (those fields are NOT part of the natural key); merging endpoint lists from two sources without dedup; trailing-slash or case differences that the canonicalizer collapses.","solutions":["Deduplicate declaredEndpoints by (host, port, transport) before submitting; remember scheme/host are lowercased and default ports are inferred (http/ws=80, https/wss=443).","Drop the duplicate entry flagged by the key string in the message, which prints as namespaceId/agentName/protocol/host:port/transport.","If the two entries legitimately differ only by priority/weight/metadata, they are NOT distinct endpoints — merge them into one Endpoint with the desired routing attributes.","Use EndpointNaturalKey.of(...) yourself to pre-compute keys and detect collisions before calling the validator."],"exampleFix":"// before\ndeclared.add(uri(\"grpc://svc:8080\"));\ndeclared.add(uri(\"grpc://svc:8080\")); // same natural key -> rejected\n// after\ndeclared.add(uri(\"grpc://svc:8080\"));\ndeclared.add(uri(\"grpc://svc:8081\")); // distinct host:port","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 (Endpoint e : callInterface.getDeclaredEndpoints()) {\n    EndpointNaturalKey k = EndpointNaturalKey.of(namespaceId, agentName, protocol, e);\n    if (!seen.add(k)) {\n        throw new IllegalStateException(\"duplicate declared endpoint: \" + k);\n    }\n}","typeGuard":"// Java collision pre-check\nstatic boolean noDeclaredDuplicates(String ns, String name, String proto, List<Endpoint> eps) {\n    Set<EndpointNaturalKey> s = new HashSet<>();\n    for (Endpoint e : eps) {\n        if (!s.add(EndpointNaturalKey.of(ns, name, proto, e))) return false;\n    }\n    return true;\n}","tryCatchPattern":"try {\n    AgentModelValidator.validateAgent(agent);\n} catch (IllegalArgumentException e) {\n    if (e.getMessage().startsWith(\"Duplicate declared Endpoint\")) {\n        // parse the key, dedup the list, retry\n    } else throw e;\n}","preventionTips":["Remember URI scheme/host are lowercased and default ports are inferred in the natural key.","priority/weight/metadata do NOT make endpoints distinct.","Dedup by host:port:transport before submission."],"tags":["ai-agent","validation","duplicate","endpoints"],"backgroundTag":null,"analyzedSha":"9b989acdf181d00898f2e8839257bb2b2a3cefe3","analyzedAt":"2026-08-14T07:17:31.569Z","schemaVersion":2},"datasetVersion":"2026-08-14T10:17:34.591Z"}