alibaba/spring-ai-alibaba · error · Exception
Unable to delete retained checkpoints
Error message
Unable to delete retained checkpoints
What it means
PostgresSaver.deleteCheckpoints wraps any SQLException from executing the batched DELETE of retained checkpoint ids into Exception('Unable to delete retained checkpoints'). This is used when pruning checkpoints that fall outside the retention policy, so failure leaves stale rows behind.
Source
Thrown at spring-ai-alibaba-graph-core/src/main/java/com/alibaba/cloud/ai/graph/checkpoint/savers/postgresql/PostgresSaver.java:570
}
@Override
protected void deleteCheckpoints(String threadId, Collection<String> checkpointIds) throws Exception {
if (checkpointIds.isEmpty()) {
return;
}
try (Connection conn = getConnection();
PreparedStatement ps = conn.prepareStatement(
DELETE_CHECKPOINTS.formatted(String.join(", ", Collections.nCopies(checkpointIds.size(), "?"))))) {
ps.setString(1, threadId);
int index = 2;
for (String checkpointId : checkpointIds) {
ps.setObject(index++, UUID.fromString(checkpointId), Types.OTHER);
}
ps.executeUpdate();
}
catch (SQLException ex) {
throw new Exception("Unable to delete retained checkpoints", ex);
}
}
@Override
protected void releaseThread(String threadId) throws Exception {
Connection conn = null;
try (Connection ignored = conn = getConnection()) {
conn.setAutoCommit(false);
log.trace("Executing release Thread:\n---\n{}---", RELEASE_THREAD);
try (PreparedStatement ps = conn.prepareStatement(RELEASE_THREAD)) {
ps.setString(1, threadId);
int rowsAffected = ps.executeUpdate();
if (rowsAffected == 0) {
conn.rollback();
throw new IllegalStateException(format("Thread '%s' not found or already released", threadId));
}
}
conn.commit();View on GitHub (pinned to f82da0b50f)
Solutions
- Check the wrapped SQLException for privilege/schema issues; grant DELETE on the checkpoint table if denied.
- Verify all checkpoint ids are valid UUID strings matching stored values.
- Confirm the checkpoint table exists and matches the current schema.
- Retry the deletion; the operation is safe to re-run since it removes rows by id.
Example fix
// before // GRANT SELECT, INSERT ON checkpoints TO app_user; -- DELETE missing // after // GRANT SELECT, INSERT, UPDATE, DELETE ON checkpoints TO app_user; saver.deleteCheckpoints(threadId, retainedIds);
Defensive patterns
Strategy: try-catch
Validate before calling
// Java: validate ids before deletion retainedIds.forEach(id -> UUID.fromString(id)); // throws early on malformed ids
Try / catch
try {
saver.deleteCheckpoints(threadId, retainedIds);
} catch (Exception e) {
if ("Unable to delete retained checkpoints".equals(e.getMessage()) && e.getCause() instanceof SQLException sql) {
// check privilege/schema; deletion is idempotent so retry later
}
} Prevention
- Grant DELETE privilege on the checkpoints table to the app user.
- Store and pass checkpoint ids exactly as returned (UUID format).
- Treat cleanup failures as non-fatal; rows can be pruned later.
- Schedule retention cleanup away from heavy write windows.
When it happens
Trigger: Deleting retained (obsolete) checkpoint ids when the DELETE fails: connection loss, malformed checkpoint id that cannot be parsed as UUID (UUID.fromString throws IllegalArgumentException before SQL, otherwise SQL error), table missing, or permission denied on DELETE.
Common situations: User lacks DELETE privilege on the checkpoints table; checkpoint ids in an unexpected format; DB connectivity drops during cleanup; retention cleanup running at the same time as writes causing contention.
Related errors
- Unable to delete retained checkpoints
- Unable to delete retained checkpoints
- Unable to load checkpoints
- Unable to load latest checkpoint
- Unable to load checkpoint
AI-assisted analysis of alibaba/spring-ai-alibaba@f82da0b50f (2026-09-09).
Data as JSON: /api/errors/82d4357fb0bdb337.
Report an issue: GitHub.