{"record":{"id":"0e3d064cb4fca2aa","repo":"apache/skywalking","slug":"the-metrics-in-expression-expression-must-have","errorCode":null,"errorMessage":"The metrics in expression: {expression} must have the same scope level, but got: {scopeSet}.","messagePattern":"The metrics in expression: (.+?) must have the same scope level, but got: (.+?)\\.","errorType":"validation","errorClass":"IllegalExpressionException","httpStatus":null,"severity":"error","filePath":"oap-server/server-alarm-plugin/src/main/java/org/apache/skywalking/oap/server/core/alarm/provider/AlarmRule.java","lineNumber":117,"sourceCode":"                    \"Expression: \" + expression + \" is not a SINGLE_VALUE result expression.\");\n            }\n\n            verifyIncludeMetrics(visitor.getIncludeMetrics(), expression);\n            this.expression = expression;\n            this.includeMetrics = visitor.getIncludeMetrics();\n            this.maxTrendRange = visitor.getMaxTrendRange();\n        } finally {\n            TRACE_CONTEXT.remove();\n        }\n    }\n\n    private void verifyIncludeMetrics(Set<String> includeMetrics, String expression) throws IllegalExpressionException {\n        Set<String> scopeSet = new HashSet<>();\n        for (String metricName : includeMetrics) {\n            scopeSet.add(ValueColumnMetadata.INSTANCE.getScope(metricName).name());\n        }\n        if (scopeSet.size() != 1) {\n            throw new IllegalExpressionException(\n                \"The metrics in expression: \" + expression + \" must have the same scope level, but got: \" + scopeSet + \".\");\n        }\n    }\n}\n","sourceCodeStart":99,"sourceCodeEnd":122,"githubUrl":"https://github.com/apache/skywalking/blob/102af09b4a56064e22050dded10e2c52e490d040/oap-server/server-alarm-plugin/src/main/java/org/apache/skywalking/oap/server/core/alarm/provider/AlarmRule.java#L99-L122","documentation":"Thrown when the metrics referenced by an alarm rule expression belong to different scope levels (e.g. one ServiceLevel metric and one EndpointLevel metric). An alarm rule is evaluated per entity of one scope, so all includeMetrics must share a single scope; the set of distinct scope names found in ValueColumnMetadata is printed in the message.","triggerScenarios":"Calling AlarmRule.setExpression with an expression mixing metrics of different scopes, e.g. `service_resp_time / endpoint_cpm > 10`, or a metric name that resolves to a different scope than the others in the same expression. The verifyIncludeMetrics loop collects ValueColumnMetadata.INSTANCE.getScope(metricName).name() for every referenced metric and throws when the set size != 1.","commonSituations":"Building a ratio expression from two metrics that look related but are defined at different levels (service vs endpoint, instance vs service); activating a metric via OAL at a custom scope and reusing it in an existing alarm expression; renaming/moving a metric with a new scope in a new OAP version.","solutions":["Read the printed scopeSet in the message to see which scopes conflict, then replace the odd metric with the equivalent metric of the dominant scope (e.g. use service_sla instead of endpoint_sla).","If the cross-scope comparison is genuinely needed, express it as two separate rules or pre-aggregate the metric at the needed scope in OAL.","Confirm each metric name in the expression exists and its scope by checking the metrics metadata (MetricsDAO/ValueColumnMetadata, or the UI metric list) before writing the rule.","Fix alarm-settings.yml and restart OAP / re-push the dynamic config; the whole rule file load aborts on this exception."],"exampleFix":"# before\nrules:\n  mixed_rule:\n    expression: service_resp_time > endpoint_percentile(95)  # Service vs Endpoint scopes\n# after\nrules:\n  mixed_rule:\n    expression: service_resp_time > service_percentile(95)  # same scope","handlingStrategy":"validation","validationCode":"// Before setExpression, assert all referenced metrics share one scope:\nSet<String> scopes = includeMetrics.stream()\n    .map(m -> ValueColumnMetadata.INSTANCE.getScope(m).name())\n    .collect(Collectors.toSet());\nif (scopes.size() != 1) throw new IllegalArgumentException(\"mixed scopes: \" + scopes);","typeGuard":null,"tryCatchPattern":"catch (IllegalExpressionException e) {\n    // message already contains the conflicting scopeSet; log and reject the rule\n    log.error(\"Rejecting alarm rule {}: {}\", ruleName, e.getMessage());\n}","preventionTips":["Check each metric's scope in the metrics metadata (UI or ValueColumnMetadata) before combining metrics in one expression.","Keep expressions within one domain (all service_*, or all endpoint_*, etc.).","Add a CI test that loads alarm-settings.yml and asserts every rule constructs successfully."],"tags":["alarm","mqe","scope","config"],"backgroundTag":null,"analyzedSha":"102af09b4a56064e22050dded10e2c52e490d040","analyzedAt":"2026-08-14T10:47:52.647Z","schemaVersion":2},"datasetVersion":"2026-08-15T17:31:12.345Z"}