redis/redis-py · error · ValueError

Bad query

Error message

Bad query

What it means

Raised by the sync aggregate() (redis/commands/search/commands.py:1449) as a ValueError when the query argument is neither an AggregateRequest nor a Cursor. FT.AGGREGATE requires one of these typed request objects; a raw string, dict, or Query (which belongs to search()) is rejected.

Solutions

  1. Build an AggregateRequest and pass that: index.aggregate(AggregateRequest('*').group_by('@field', reducers...)).
  2. For cursor-based paging, pass a Cursor object returned by a prior aggregation.
  3. Use search() with a Query for non-aggregating queries.

Example fix

# before
index.aggregate('@genre:{rock}')
# after
from redis.commands.search.aggregation import AggregateRequest, Asc
index.aggregate(AggregateRequest('*').group_by('@genre', Asc('@genre')))
Defensive patterns

Strategy: type-guard

Validate before calling

from redis.commands.search.aggregation import AggregateRequest, Cursor

def safe_aggregate(index, query, query_params=None):
    if not isinstance(query, (AggregateRequest, Cursor)):
        raise TypeError('aggregate() requires an AggregateRequest or Cursor')
    return index.aggregate(query, query_params=query_params)

Type guard

from redis.commands.search.aggregation import AggregateRequest, Cursor

def is_aggregate_query(q) -> bool:
    return isinstance(q, (AggregateRequest, Cursor))

Try / catch

try:
    index.aggregate(query)
except ValueError as e:
    if 'Bad query' in str(e):
        raise TypeError('pass AggregateRequest or Cursor to aggregate()') from e
    raise

Prevention

When it happens

Trigger: Calling index.aggregate('SELECT *'), index.aggregate(some_query_object), index.aggregate(None), or index.aggregate({...}). A Query object is for FT.SEARCH, not FT.AGGREGATE.

Common situations: Confusing search Query with AggregateRequest, passing a raw RQL string, or forgetting to wrap the request. Note this is the sync path - the async mirror is error 355.

Related errors


AI-assisted analysis of redis/redis-py@6a6b581b48 (2026-08-10). Data as JSON: /api/errors/e0a6077389b0365f. Report an issue: GitHub.

Appendix: source

Thrown at redis/commands/search/commands.py:1449

        Issue an aggregation query.

        ### Parameters

        **query**: This can be either an `AggregateRequest`, or a `Cursor`

        An `AggregateResult` object is returned. You can access the rows from
        its `rows` property, which will always yield the rows of the result.

        For more information see `FT.AGGREGATE <https://redis.io/commands/ft.aggregate>`_.
        """  # noqa
        if isinstance(query, AggregateRequest):
            has_cursor = bool(query._cursor)
            cmd = [AGGREGATE_CMD, self.index_name] + query.build_args()
        elif isinstance(query, Cursor):
            has_cursor = True
            cmd = [CURSOR_CMD, "READ", self.index_name] + query.build_args()
        else:
            raise ValueError("Bad query", query)
        cmd += self.get_params_args(query_params)

        raw = self.execute_command(*cmd)
        return self._parse_results(
            AGGREGATE_CMD, raw, query=query, has_cursor=has_cursor
        )

    def _get_aggregate_result(
        self, raw: List, query: Union[AggregateRequest, Cursor], has_cursor: bool
    ):
        if has_cursor:
            if isinstance(query, Cursor):
                query.cid = raw[1]
                cursor = query
            else:
                cursor = Cursor(raw[1])
            raw = raw[0]
        else:

View on GitHub (pinned to 6a6b581b48)