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
- Build an AggregateRequest and pass that: index.aggregate(AggregateRequest('*').group_by('@field', reducers...)).
- For cursor-based paging, pass a Cursor object returned by a prior aggregation.
- 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
- Always wrap aggregations in AggregateRequest(...).
- Use search(Query(...)) for non-aggregating queries.
- Do not pass a raw query string to aggregate().
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
- Bad query type
- Cannot use FIELDNAME alias with no field
- 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@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)