{"record":{"id":"03fa02696ab4da09","repo":"quickwit-oss/tantivy","slug":"unexpected-metric-type","errorCode":null,"errorMessage":"unexpected metric type","messagePattern":"unexpected metric type","errorType":"panic","errorClass":null,"httpStatus":null,"severity":"error","filePath":"src/aggregation/intermediate_agg_result.rs","lineNumber":481,"sourceCode":"                // buckets serialize as `\"value\": 0`, not `\"value\": null`.\n                // The non-ES `none_if_no_match` flag on `SumAggregation`\n                // opts into SQL-style `null` for downstream consumers.\n                let none_if_no_match = req\n                    .agg\n                    .as_sum()\n                    .and_then(|sum| sum.none_if_no_match)\n                    .unwrap_or(false);\n                let value = intermediate_sum.finalize();\n                if none_if_no_match {\n                    MetricResult::Sum(value.into())\n                } else {\n                    let value = Some(value.unwrap_or(0.0));\n                    MetricResult::Sum(value.into())\n                }\n            }\n            IntermediateMetricResult::Percentiles(percentiles) => MetricResult::Percentiles(\n                percentiles\n                    .into_final_result(req.agg.as_percentile().expect(\"unexpected metric type\")),\n            ),\n            IntermediateMetricResult::TopHits(top_hits) => {\n                MetricResult::TopHits(top_hits.into_final_result())\n            }\n            IntermediateMetricResult::Cardinality(cardinality) => {\n                MetricResult::Cardinality(cardinality.finalize().into())\n            }\n        }\n    }\n\n    pub(crate) fn get_value(&self, agg_property: &str) -> crate::Result<Option<f64>> {\n        match self {\n            IntermediateMetricResult::Average(avg) => Ok(avg.finalize()),\n            IntermediateMetricResult::Count(count) => Ok(count.finalize()),\n            IntermediateMetricResult::Max(max) => Ok(max.finalize()),\n            IntermediateMetricResult::Min(min) => Ok(min.finalize()),\n            IntermediateMetricResult::Stats(stats) => stats.finalize().get_value(agg_property),\n            IntermediateMetricResult::ExtendedStats(stats) => {","sourceCodeStart":463,"sourceCodeEnd":499,"githubUrl":"https://github.com/quickwit-oss/tantivy/blob/b5d8deb80c26924e6b007a5b1a7630f35ca64de4/src/aggregation/intermediate_agg_result.rs#L463-L499","documentation":"A panic in `into_final_metric_result`: the intermediate result is `IntermediateMetricResult::Percentiles`, and the code asserts via `req.agg.as_percentile().expect(...)` that the corresponding request aggregation is a percentiles aggregation. The expect fires when the request's agg definition is a different metric type, meaning the intermediate metric result and its request definition are out of sync.","triggerScenarios":"Converting intermediate aggregation results to final results when `req.agg` is not a percentile aggregation while the intermediate value is `IntermediateMetricResult::Percentiles` — caused by mismatched agg IDs, a corrupt aggregation tree, or results merged across incompatible request definitions.","commonSituations":"Distributed queries where nodes return intermediate results for a different aggregation layout than the coordinator's request; caching intermediate results keyed incorrectly; custom code reordering or rebuilding the aggregation request tree.","solutions":["Ensure the aggregation request used for finalization is the exact request that produced the intermediate results.","Verify cluster nodes run the same engine version (intermediate result format compatibility).","Do not reuse or remap intermediate agg results across different requests or reordered aggregation trees.","If you maintain the code, use `as_percentile().ok_or(...)` to return a typed error instead of panicking."],"exampleFix":"// before\nreq.agg.as_percentile().expect(\"unexpected metric type\")\n// after\nreq.agg.as_percentile()\n    .ok_or_else(|| crate::AggregationError::Internal(\"expected percentiles agg\".to_string()))?","handlingStrategy":"validation","validationCode":"// Verify the aggregation type in the request matches before finalizing percentiles results.\nfn is_percentiles_agg(req: &Aggregation) -> bool {\n    matches!(req, Aggregation::Percentiles(_))\n}","typeGuard":"fn as_percentiles(req: &Aggregation) -> Option<&PercentilesAgg> {\n    if let Aggregation::Percentiles(p) = req { Some(p) } else { None }\n}","tryCatchPattern":"let res = std::panic::catch_unwind(|| intermediate.into_final_result(&req, limits));\nmatch res {\n    Ok(r) => r,\n    Err(_) => return internal_error(\"intermediate/result type mismatch\"),\n}","preventionTips":["Always finalize with the exact request that produced the intermediate results.","Keep engine versions uniform across the cluster.","Never cache intermediate agg results without keying on the full request."],"tags":["rust","panic","aggregation","type-mismatch","internal-invariant"],"backgroundTag":"aggregation-type-mismatch","analyzedSha":"b5d8deb80c26924e6b007a5b1a7630f35ca64de4","analyzedAt":"2026-09-05T13:20:51.521Z","contentChangedAt":"2026-09-05T13:20:51.521Z","schemaVersion":2},"datasetVersion":"2026-09-12T17:17:11.597Z"}