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
- Wrap your query: client.aggregate(AggregateRequest('*').group_by(...)).
- For cursor reads, pass a Cursor object returned by a previous aggregate().
- 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
- aggregate() never accepts a plain string — always wrap in AggregateRequest.
- Distinguish search() (accepts str/Query) from aggregate() (AggregateRequest/Cursor).
- Keep a factory that returns the right wrapper type for each API.
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
- Cannot use FIELDNAME alias with no field
- Bad query type {type(query)}
- Must provide AggregateRequest object or Query object.
- Non-Sortable non-Indexable fields are ignored
- Cannot set 'sortable' or 'no_index' in Vector fields.
AI-assisted analysis of redis/redis-py@da03cdc7e8 (2026-08-04).
Data as JSON: /data/errors/e0a6077389b0365f.json.
Report an issue: GitHub.