{"record":{"id":"ffe13daf491375e6","repo":"alibaba/nacos","slug":"runtime-endpoint-state-must-be-expected","errorCode":null,"errorMessage":"Runtime Endpoint state must be {expected}","messagePattern":"Runtime Endpoint state must be (.+?)","errorType":"validation","errorClass":"IllegalArgumentException","httpStatus":null,"severity":"error","filePath":"api/src/main/java/com/alibaba/nacos/api/ai/utils/AgentModelValidator.java","lineNumber":610,"sourceCode":"                \"Snapshot binding does not match selected Version: \" + selectedVersion);\n        }\n        String bindingKey = runtimeVersion + \"\\u0000\" + versionRange.getValue();\n        if (!bindingKeys.add(bindingKey)) {\n            throw new IllegalArgumentException(\"Duplicate Runtime Version binding\");\n        }\n    }\n    \n    private static void validateRuntimeEndpointState(RuntimeEndpointSnapshotItem item) {\n        RuntimeEndpointState expected;\n        if (!item.getEnabled()) {\n            expected = RuntimeEndpointState.DISABLED;\n        } else if (!item.getHealthy()) {\n            expected = RuntimeEndpointState.UNHEALTHY;\n        } else {\n            expected = RuntimeEndpointState.AVAILABLE;\n        }\n        if (item.getState() != expected) {\n            throw new IllegalArgumentException(\n                \"Runtime Endpoint state must be \" + expected.name());\n        }\n    }\n    \n    private static void validateEndpoint(Endpoint endpoint) {\n        requireNonNull(endpoint, \"Endpoint\");\n        EndpointCanonicalizer.canonicalize(endpoint);\n        if (endpoint.getHealthy() != null) {\n            throw new IllegalArgumentException(\"Management or declared Endpoint forbids healthy\");\n        }\n    }\n    \n    private static void validateAbsoluteUri(String value, String fieldName) {\n        if (value.isEmpty() || codePointLength(value) > MAX_URI_LENGTH) {\n            throw new IllegalArgumentException(\"Invalid \" + fieldName + \": \" + value);\n        }\n        try {\n            URI uri = new URI(value);","sourceCodeStart":592,"sourceCodeEnd":628,"githubUrl":"https://github.com/alibaba/nacos/blob/9b989acdf181d00898f2e8839257bb2b2a3cefe3/api/src/main/java/com/alibaba/nacos/api/ai/utils/AgentModelValidator.java#L592-L628","documentation":"Thrown by validateRuntimeEndpointState when item.state does not equal the state derived from enabled/healthy. The validator computes the expected RuntimeEndpointState deterministically: if enabled is false -> DISABLED; else if healthy is false -> UNHEALTHY; else AVAILABLE. The caller-supplied state must match that derivation exactly; state is treated as a redundant, verifiable field rather than an independent input, so clients cannot claim AVAILABLE for an unhealthy or disabled endpoint.","triggerScenarios":"A RuntimeEndpointSnapshotItem where (enabled, healthy, state) are inconsistent. Examples: enabled=false, healthy=true, state=AVAILABLE -> expected DISABLED, rejected. enabled=true, healthy=false, state=AVAILABLE -> expected UNHEALTHY, rejected. enabled=true, healthy=true, state=DISABLED -> expected AVAILABLE, rejected. Reached at the tail of validateRuntimeEndpointSnapshotItem.","commonSituations":"Health-check outputting a new healthy=false but the state field left at AVAILABLE from a previous serialization; toggling enabled without recomputing state; an aggregator that copies state from one source and enabled/healthy from another; deserializing a stale snapshot after a health flip.","solutions":["Derive state from enabled/healthy instead of setting it independently: !enabled -> DISABLED; !healthy -> UNHEALTHY; otherwise AVAILABLE.","Recompute and rewrite item.state whenever enabled or healthy changes, before pushing the snapshot.","If your data source only gives you enabled/healthy, omit state computation and set it from the rule above; never carry a stale state.","Centralize the (enabled,healthy)->state mapping in one helper so all push paths stay consistent."],"exampleFix":"// before\nitem.setEnabled(false);\nitem.setHealthy(true);\nitem.setState(RuntimeEndpointState.AVAILABLE); // mismatch -> rejected\n// after\nRuntimeEndpointState s = !item.getEnabled() ? RuntimeEndpointState.DISABLED\n    : !item.getHealthy() ? RuntimeEndpointState.UNHEALTHY\n    : RuntimeEndpointState.AVAILABLE;\nitem.setState(s);","handlingStrategy":"validation","validationCode":"import com.alibaba.nacos.api.ai.model.agent.RuntimeEndpointState;\n\nfor (RuntimeEndpointSnapshotItem it : snapshot.getItems()) {\n    RuntimeEndpointState expected = !it.getEnabled() ? RuntimeEndpointState.DISABLED\n        : !it.getHealthy() ? RuntimeEndpointState.UNHEALTHY\n        : RuntimeEndpointState.AVAILABLE;\n    if (it.getState() != expected) {\n        it.setState(expected); // self-heal before push\n    }\n}","typeGuard":"static boolean stateConsistent(RuntimeEndpointSnapshotItem it) {\n    RuntimeEndpointState expected = !it.getEnabled() ? RuntimeEndpointState.DISABLED\n        : !it.getHealthy() ? RuntimeEndpointState.UNHEALTHY\n        : RuntimeEndpointState.AVAILABLE;\n    return it.getState() == expected;\n}","tryCatchPattern":"try {\n    AgentModelValidator.validateRuntimeEndpointSnapshot(snapshot);\n} catch (IllegalArgumentException e) {\n    if (e.getMessage().startsWith(\"Runtime Endpoint state must be\")) {\n        // recompute state from enabled/healthy and retry\n    } else throw e;\n}","preventionTips":["Never set state independently — derive it from enabled/healthy.","Centralize the (enabled,healthy)->state rule in one helper used by all push paths.","Recompute state whenever enabled or healthy changes."],"tags":["ai-agent","validation","state-invariant","runtime-snapshot"],"backgroundTag":null,"analyzedSha":"9b989acdf181d00898f2e8839257bb2b2a3cefe3","analyzedAt":"2026-08-14T07:17:31.569Z","schemaVersion":2},"datasetVersion":"2026-08-14T10:17:34.591Z"}