redis/jedis · info
Failed to delete cursor
Error message
Failed to delete cursor {}: {} What it means
AggregateIterator deletes the server-side cursor of a FT.AGGREGATE cursor-based aggregation when the iteration is closed or removed. If the cursor deletion command fails, this warning is logged instead of throwing, because the cursor will expire naturally on the server and the user's close() should not fail. The exception message (not the exception) is included in the log.
Solutions
- No user action required — the cursor expires server-side; treat the warning as informational.
- If warnings are frequent, check connection stability and pool health to the search endpoint.
- Ensure the iterator is consumed or closed promptly so cursors are deleted before TTL expiry.
- Upgrade jedis/search module if you see repeated failures on valid cursors (protocol bugs).
Example fix
// before
try (AggregateIterator it = ...; ) { consume(it); } // warnings ignored by design
// after
// nothing to change: close() deliberately swallows cursor-delete errors Defensive patterns
Strategy: try-catch
Validate before calling
// ensure the connection is healthy before long aggregations if (!jedis.isConnected()) jedis = reconnect();
Try / catch
try (AggregateIterator it = jedis.ftAggregateCursor(...)) {
while (it.hasNext()) consume(it.next());
} // close() handles cursor deletion; failures are logged, not thrown Prevention
- Close aggregation iterators promptly (try-with-resources)
- Don't hold cursors idle longer than their TTL
- Keep connections to the search endpoint stable (pools, keepalive)
- Treat the warning as informational — cursors expire server-side
When it happens
Trigger: Calling close() or remove() on an AggregateIterator whose CURSOR DEL command fails: connection dropped mid-aggregation, server restarted, cursor already expired/deleted, or search module error.
Common situations: Abandoning aggregations early in apps with unstable connections; Redis Stack search module restarted between fetches; cursor left idle past its TTL so the DEL arrives on an already-gone cursor; network blips during result processing.
Understand the failure class
Background: 'Could not be found', 'does not exist', 'not found in database': the resource-not-found family when an ID, slug, key, or URI lookup comes back empty — this error's family across 20 libraries.
Related errors
- Failed to fetch next aggregation batch
- Failed to initialize aggregation cursor
- cursor must be set
- null is not a valid argument.
- DIALECT=0 cannot be set.
AI-assisted analysis of redis/jedis@6dac31d4c2 (2026-09-08).
Data as JSON: /api/errors/ca7eacf2b1e9d5e9.
Report an issue: GitHub.
Appendix: source
Thrown at src/main/java/redis/clients/jedis/search/aggr/AggregateIterator.java:175
// NOTE(imalinovskyi): If we use single connection to execute commands, we're not
// responsible for closing it here.
}
/**
* Deletes the cursor on the server to free resources. This method is idempotent and safe to call
* multiple times.
*/
private void deleteCursor() {
if (cursorId != null && cursorId > 0) {
CommandArguments args = new CommandArguments(SearchProtocol.SearchCommand.CURSOR)
.add(SearchProtocol.SearchKeyword.DEL).add(indexName).add(cursorId);
try {
// Delete the cursor to free server resources
executeCommand(args);
} catch (Exception e) {
// Log but don't throw - cursor will expire naturally
logger.warn("Failed to delete cursor {}: {}", cursorId, e.getMessage());
}
}
}
private AggregationResult doFetch() {
if (cursorId == null || cursorId <= 0) {
return null;
}
CommandArguments args = new CommandArguments(SearchProtocol.SearchCommand.CURSOR)
.add(SearchProtocol.SearchKeyword.READ).add(indexName).add(cursorId);
// Only add COUNT argument if a batch size was explicitly specified
if (batchSize != null) {
args.add(SearchProtocol.SearchKeyword.COUNT).add(batchSize);
}
Object rawReply = executeCommand(args);View on GitHub (pinned to 6dac31d4c2)