redis/redis-py · error · ValueError

Bad query

Error message

Bad query

What it means

Raised by the sync aggregate() when the query argument is neither an AggregateRequest nor a Cursor. Unlike search(), aggregate() does NOT accept a plain string — you must wrap your query in an AggregateRequest. The error is raised with two args ("Bad query", query) so str(exc) shows both.

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 da03cdc7e8)

Solutions

  1. Wrap your query: client.aggregate(AggregateRequest('*').group_by(...)).
  2. For cursor reads, pass a Cursor object returned by a previous aggregate().
  3. Use client.search(Query(...)) if you wanted a plain search rather than aggregation.

Example fix

// before
client.aggregate('@title:hello')
// after
from redis.commands.search.aggregation import AggregateRequest
client.aggregate(AggregateRequest('@title:hello'))
Defensive patterns

Strategy: type-guard

Validate before calling

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

def safe_aggregate(client, query, query_params=None):
    if isinstance(query, str):
        query = AggregateRequest(query)
    if not isinstance(query, (AggregateRequest, Cursor)):
        raise TypeError(f"query must be AggregateRequest/Cursor, got {type(query)}")
    return client.aggregate(query, query_params=query_params)

Type guard

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

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

Try / catch

try:
    client.aggregate(query)
except ValueError as e:
    if "Bad query" in str(e):
        from redis.commands.search.aggregation import AggregateRequest
        client.aggregate(AggregateRequest(str(query)))
    else:
        raise

Prevention

When it happens

Trigger: Call client.aggregate('some query string') or client.aggregate(123) — passing anything other than AggregateRequest(...) or Cursor(...).

Common situations: Assuming aggregate() accepts a string like search() does; passing a Query object (wrong type — Query is for search/explain); passing a raw cursor id instead of a Cursor object.

Related errors


AI-assisted analysis of redis/redis-py@da03cdc7e8 (2026-08-04). Data as JSON: /data/errors/e0a6077389b0365f.json. Report an issue: GitHub.