alibaba/spring-ai-alibaba · error · Exception

Unable to delete retained checkpoints

Error message

Unable to delete retained checkpoints

What it means

deleteCheckpoints() removes a set of checkpoint ids for a thread and wraps any SQLException from the batched DELETE into an Exception with this message. Despite the wording ('retained checkpoints'), it fails when deleting the specified checkpoints, typically due to a database-level problem.

Solutions

  1. Check the wrapped SQLException for the exact failure (lock timeout, parameter limit).
  2. Split large checkpointId collections into smaller batches for deletion.
  3. Verify the checkpoints table exists and the DB user has DELETE privileges.
  4. Reduce lock contention by pruning checkpoints outside peak load or in smaller transactions.

Example fix

// before
saver.deleteCheckpoints(threadId, thousandsOfIds); // may exceed IN limits
// after
for (List<String> batch : Lists.partition(ids, 500)) {
    saver.deleteCheckpoints(threadId, batch);
}
Defensive patterns

Strategy: try-catch

Validate before calling

if (ids.size() > 1000) throw new IllegalArgumentException("batch checkpoint deletions, got " + ids.size());

Try / catch

try {
    saver.deleteCheckpoints(threadId, ids);
} catch (Exception e) {
    log.error("checkpoint prune failed for {}", threadId, e.getCause());
    // non-fatal: schedule retry
}

Prevention

When it happens

Trigger: Checkpoint history pruning (release/releaseThread with retained ids) when the DELETE fails: connection loss, too many parameters for the IN clause, lock contention, or missing/corrupt table.

Common situations: Very large checkpoint lists exceeding DB limits for IN-clause parameters; long-running transactions holding row locks on the checkpoint table; table dropped or renamed after a migration.

Related errors


AI-assisted analysis of alibaba/spring-ai-alibaba@f82da0b50f (2026-09-09). Data as JSON: /api/errors/01fe45b082b8cbcd. Report an issue: GitHub.

Appendix: source

Thrown at spring-ai-alibaba-graph-core/src/main/java/com/alibaba/cloud/ai/graph/checkpoint/savers/mysql/MysqlSaver.java:554

	}

	@Override
	protected void deleteCheckpoints(String threadName, Collection<String> checkpointIds) throws Exception {
		if (checkpointIds.isEmpty()) {
			return;
		}
		try (Connection connection = dataSource.getConnection();
				PreparedStatement preparedStatement = connection.prepareStatement(
						DELETE_CHECKPOINTS.formatted(String.join(", ", Collections.nCopies(checkpointIds.size(), "?"))))) {
			preparedStatement.setString(1, threadName);
			int index = 2;
			for (String checkpointId : checkpointIds) {
				preparedStatement.setString(index++, checkpointId);
			}
			preparedStatement.executeUpdate();
		}
		catch (SQLException ex) {
			throw new Exception("Unable to delete retained checkpoints", ex);
		}
	}

	@Override
	protected void releaseThread(String threadName) throws Exception {
		Connection conn = null;
		try (Connection ignored = conn = dataSource.getConnection()) {
			conn.setAutoCommit(false);

			try (PreparedStatement preparedStatement = conn.prepareStatement(RELEASE_THREAD)) {
				preparedStatement.setString(1, threadName);
				int rowsAffected = preparedStatement.executeUpdate();
				if (rowsAffected == 0) {
					conn.rollback();
					throw new IllegalStateException(format("Thread '%s' not found or already released", threadName));
				}
			}

View on GitHub (pinned to f82da0b50f)