{"record":{"id":"c7042d3b2fa155d6","repo":"alibaba/nacos","slug":"client-disconnect","errorCode":"CLIENT_DISCONNECT","errorMessage":"AI client connection already disconnected: ${parentClientId}","messagePattern":"AI client connection already disconnected: (.+?)","errorType":"exception","errorClass":"NacosRuntimeException","httpStatus":403,"severity":"critical","filePath":"ai/src/main/java/com/alibaba/nacos/ai/service/a2a/CanonicalA2aEndpointOperationService.java","lineNumber":178,"sourceCode":"    @Override\n    public void clientDisConnected(Connection connect) {\n        if (!RemoteConstants.LABEL_MODULE_AI\n            .equals(connect.getMetaInfo().getLabel(RemoteConstants.LABEL_MODULE))) {\n            return;\n        }\n        Set<String> children = childClientIds.remove(connect.getMetaInfo().getConnectionId());\n        if (children == null) {\n            return;\n        }\n        for (String childClientId : children) {\n            clientManager.clientDisconnected(childClientId);\n        }\n    }\n    \n    private String ensureChildClient(String parentClientId, String namespaceId, String agentName,\n        String version) {\n        if (!clientManager.contains(parentClientId)) {\n            throw new NacosRuntimeException(NacosException.CLIENT_DISCONNECT,\n                \"AI client connection already disconnected: \" + parentClientId);\n        }\n        String childClientId = childClientId(parentClientId, namespaceId, agentName, version);\n        if (!clientManager.contains(childClientId)) {\n            ClientAttributes attributes = new ClientAttributes();\n            attributes.addClientAttribute(ClientConstants.CONNECTION_TYPE,\n                ClientConstants.DEFAULT_FACTORY);\n            clientManager.clientConnected(childClientId, attributes);\n        }\n        childClientIds.computeIfAbsent(parentClientId,\n            key -> ConcurrentHashMap.newKeySet()).add(childClientId);\n        if (!clientManager.contains(parentClientId)) {\n            disconnectChild(parentClientId, childClientId);\n            throw new NacosRuntimeException(NacosException.CLIENT_DISCONNECT,\n                \"AI client connection already disconnected: \" + parentClientId);\n        }\n        return childClientId;\n    }","sourceCodeStart":160,"sourceCodeEnd":196,"githubUrl":"https://github.com/alibaba/nacos/blob/9b989acdf181d00898f2e8839257bb2b2a3cefe3/ai/src/main/java/com/alibaba/nacos/ai/service/a2a/CanonicalA2aEndpointOperationService.java#L160-L196","documentation":"Thrown by CanonicalA2aEndpointOperationService.ensureChildClient when the parent AI gRPC client connection (parentClientId) is no longer present in clientManager at the start of child-client creation. This is a NacosRuntimeException with CLIENT_DISCONNECT code, indicating the originating connection dropped before or during endpoint registration.","triggerScenarios":"The AI agent's gRPC connection disconnected (network failure, client shutdown, server-side eviction) before the register call reached ensureChildClient. A deregister or reconnect happened concurrently, removing the parent client from the manager. Stale parentClientId reused after reconnect produced a new connection ID.","commonSituations":"Network instability causing the gRPC connection to drop mid-operation. Client restart that created a new connection ID while old code path still references the old one. Long-running registration that outlived a connection keepalive timeout. Server-side connection eviction under memory pressure.","solutions":["Reconnect the AI gRPC client and obtain the new parentClientId before retrying endpoint registration.","Verify clientManager.contains(parentClientId) is true before calling register.","If the connection is flaky, add retry logic that re-establishes the connection on CLIENT_DISCONNECT.","Check server logs for connection eviction reasons (idle timeout, resource pressure)."],"exampleFix":"// before\nendpointOpService.register(staleParentClientId, ns, agentName, endpoints);\n\n// after\nif (!clientManager.contains(parentClientId)) {\n    parentClientId = reconnectAiClient(); // establish new gRPC connection\n}\nendpointOpService.register(parentClientId, ns, agentName, endpoints);","handlingStrategy":"validation","validationCode":"// Verify parent connection is alive before registering\nif (!clientManager.contains(parentClientId)) {\n    parentClientId = reconnectAiGrpcClient(); // establish new connection\n}\nendpointOpService.register(parentClientId, ns, agentName, endpoints);","typeGuard":"public static boolean isParentConnected(ClientManager mgr, String parentClientId) {\n    return mgr.contains(parentClientId);\n}","tryCatchPattern":"try {\n    endpointOpService.register(parentClientId, ns, agentName, endpoints);\n} catch (NacosRuntimeException e) {\n    if (e.getCode() == NacosException.CLIENT_DISCONNECT) {\n        parentClientId = reconnectAiGrpcClient();\n        endpointOpService.register(parentClientId, ns, agentName, endpoints);\n    } else {\n        throw e;\n    }\n}","preventionTips":["Always validate the gRPC connection is alive before endpoint operations.","Implement reconnection logic that refreshes parentClientId on disconnect.","Monitor gRPC connection health and keepalive timeouts."],"tags":["a2a","endpoint","client-connection","disconnect","grpc","runtime-exception"],"backgroundTag":null,"analyzedSha":"9b989acdf181d00898f2e8839257bb2b2a3cefe3","analyzedAt":"2026-08-14T07:17:31.569Z","schemaVersion":2},"datasetVersion":"2026-08-14T10:17:34.591Z"}