SigNoz/signoz · error

unsupported aggregate operator %s for panelType %s

Error message

unsupported aggregate operator %s for panelType %s

What it means

In buildTracesQuery's no-op branch, only PanelTypeTrace and PanelTypeList are handled; any other panel type (e.g. graph) combined with the no-op aggregate operator cannot be rendered as a trace query and is rejected.

Source

Thrown at pkg/query-service/app/traces/v4/query_builder.go:349

				withSubQuery := fmt.Sprintf(withSubQueryWithLimits, constants.SIGNOZ_TRACE_DBNAME, constants.SIGNOZ_SPAN_INDEX_V3_LOCAL_TABLENAME, timeFilter, filterSubQuery)
				afterSubQuery := tracesV3.AddLimitToQuery(constants.TraceExplorerSpanCountAfterSubQuery, mq.Limit)
				if mq.Offset != 0 {
					afterSubQuery = tracesV3.AddOffsetToQuery(afterSubQuery, mq.Offset)
				}
				query = fmt.Sprintf(constants.TraceExplorerSpanCountBeforeSubQuery, constants.SIGNOZ_TRACE_DBNAME, constants.SIGNOZ_SPAN_INDEX_V3) + withSubQuery + ") " + fmt.Sprintf(afterSubQuery, constants.SIGNOZ_TRACE_DBNAME, constants.SIGNOZ_SPAN_INDEX_V3, timeFilter)
			}
			// adding this to avoid the distributed product mode error which doesn't allow global in
			query += " settings distributed_product_mode='allow', max_memory_usage=10000000000"
		} else if panelType == v3.PanelTypeList {
			if len(mq.SelectColumns) == 0 {
				return "", fmt.Errorf("select columns cannot be empty for panelType %s", panelType)
			}
			selectLabels = getSelectLabels(mq.SelectColumns)
			// add it to the select labels
			queryNoOpTmpl := fmt.Sprintf("SELECT timestamp as timestamp_datetime, span_id as spanID, trace_id as traceID,%s ", selectLabels) + "from " + constants.SIGNOZ_TRACE_DBNAME + "." + constants.SIGNOZ_SPAN_INDEX_V3 + " where %s %s" + "%s"
			query = fmt.Sprintf(queryNoOpTmpl, timeFilter, filterSubQuery, orderBy)
		} else {
			return "", fmt.Errorf("unsupported aggregate operator %s for panelType %s", mq.AggregateOperator, panelType)
		}
		return query, nil
		// ---- NOOP ends here ----
	}

	having := tracesV3.Having(mq.Having)
	if having != "" {
		having = " having " + having
	}

	groupBy := tracesV3.GroupByAttributeKeyTags(panelType, options.GraphLimitQtype, mq.GroupBy...)
	if groupBy != "" {
		groupBy = " group by " + groupBy
	}

	aggregationKey := ""
	if mq.AggregateAttribute.Key != "" {
		aggregationKey = getColumnName(mq.AggregateAttribute, true)

View on GitHub (pinned to 5069bf80b0)

Solutions

  1. Use an aggregate operator (count, avg(rate) etc.) instead of no-op for non trace/list panel types
  2. Switch the panel type to trace or list if you really need raw no-op behavior

Example fix

// before
mq.AggregateOperator = v3.AggregateOperatorNoOp
panelType = v3.PanelTypeGraph

// after
mq.AggregateOperator = v3.AggregateOperatorCount
panelType = v3.PanelTypeGraph
Defensive patterns

Strategy: validation

Validate before calling

if mq.AggregateOperator == v3.AggregateOperatorNoOp && panelType != v3.PanelTypeTrace && panelType != v3.PanelTypeList {
    return fmt.Errorf("no-op requires trace/list panel, got %s", panelType)
}

Type guard

func noopAllowed(panelType v3.PanelType) bool {
    return panelType == v3.PanelTypeTrace || panelType == v3.PanelTypeList
}

Prevention

When it happens

Trigger: Sending AggregateOperator=no-op with panelType=graph, table, or value on a traces data source query.

Common situations: Switching a panel type in the UI without changing the aggregate operator; API calls copying a list query's structure onto a graph panel.

Related errors


AI-assisted analysis of SigNoz/signoz@5069bf80b0 (2026-08-28). Data as JSON: /api/errors/978ea4d6c6aaa771. Report an issue: GitHub.