alibaba/spring-ai-alibaba · error · Exception

Unable to insert checkpoint

Error message

Unable to insert checkpoint

What it means

insertCheckpoint wraps SQLException or IOException from the INSERT of a new checkpoint (and any connection/commit/rollback activity) into an Exception 'Unable to insert checkpoint'. The transaction is rolled back first via rollback(conn, threadId), so a failed insert leaves no partial row.

Source

Thrown at spring-ai-alibaba-graph-core/src/main/java/com/alibaba/cloud/ai/graph/checkpoint/savers/h2/H2Saver.java:379

	protected void insertCheckpoint(String threadId, Checkpoint checkpoint) throws Exception {
		Connection conn = null;
		try (Connection ignored = conn = getConnection()) {
			conn.setAutoCommit(false);
			String persistedThreadId = activeThreadId(conn, threadId);
			try (PreparedStatement ps = conn.prepareStatement(INSERT_CHECKPOINT)) {
				ps.setString(1, checkpoint.getId());
				ps.setString(2, persistedThreadId);
				ps.setString(3, checkpoint.getNodeId());
				ps.setString(4, checkpoint.getNextNodeId());
				ps.setString(5, encodeState(checkpoint.getState()));
				ps.setString(6, stateSerializer.contentType());
				ps.executeUpdate();
			}
			conn.commit();
		}
		catch (SQLException | IOException ex) {
			rollback(conn, threadId);
			throw new Exception("Unable to insert checkpoint", ex);
		}
	}

	private String activeThreadId(Connection conn, String threadName) throws SQLException {
		Optional<String> activeThreadId = selectActiveThreadId(conn, threadName);
		if (activeThreadId.isPresent()) {
			return activeThreadId.get();
		}

		String persistedThreadId = UUID.randomUUID().toString();
		try (PreparedStatement ps = conn.prepareStatement(INSERT_THREAD)) {
			ps.setString(1, persistedThreadId);
			ps.setString(2, threadName);
			ps.executeUpdate();
			return persistedThreadId;
		}
		catch (SQLException ex) {
			if (isUniqueConstraintViolation(ex)) {

View on GitHub (pinned to f82da0b50f)

Solutions

  1. Check the wrapped cause and verify the H2 database is reachable and the checkpoints table exists/has the expected columns.
  2. Initialize the schema before running (use the saver's DDL or an init migration).
  3. Ensure checkpoint state is serializable by the configured stateSerializer (all state values are supported types).
  4. Confirm the H2 file is not locked by another JVM/process and there is disk space.

Example fix

// before: table never created
H2DataSource.createScriptDataSource(...) then H2Saver directly
// after: init schema first
try (Connection c = dataSource.getConnection(); Statement st = c.createStatement()) {
  st.execute(CHECKPOINTS_TABLE_DDL);
}
Defensive patterns

Strategy: retry

Validate before calling

try (Connection c = dataSource.getConnection(); ResultSet rs = c.getMetaData().getTables(null, null, "CHECKPOINTS", null)) { if (!rs.next()) throw new IllegalStateException("checkpoints table missing — run schema init"); }

Try / catch

try { saver.addCheckpoint(threadId, checkpoint); }
catch (Exception e) { if (isTransient(e.getCause())) retryWithBackoff(); else throw e; }

Prevention

When it happens

Trigger: Adding a checkpoint to a thread while: the H2 connection fails or the pool is exhausted, the checkpoints table does not exist or a column/constraint mismatches, the checkpoint state fails to serialize to the content type of stateSerializer (IOException), or the transaction commit fails.

Common situations: First run without the H2 table initialized; switching stateSerializer content type while reusing an old database; disk full or H2 file locked by another process; long graph run holding too many open connections.

Related errors


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