spring-projects/spring-ai · warning

Skipping add for session

Error message

Skipping add for session '${sessionId}': index was concurrently cleared

What it means

During LuceneToolIndex.add, if the per-session index writer was closed concurrently (e.g. by a clear() call on another thread), Lucene throws AlreadyClosedException. The index logs this warning and skips the add instead of failing, because the session was intentionally discarded. It reflects a benign race between indexing and clearing.

Solutions

  1. Treat the warning as benign: the session was cleared, so the skipped document is intentionally discarded.
  2. Synchronize session lifecycle: clear() the session only after all pending indexTool calls complete for that sessionId.
  3. Check ordering in your code — don't index tools into a session you are simultaneously clearing.
  4. If documents are needed post-clear, re-add them after the clear completes; a new SessionIndex will be created lazily.

Example fix

// before: racing clear and add
executor.submit(() -> index.indexTools(sessionId, tools));
index.clear(sessionId); // may close writer mid-add
// after: clear after indexing completes
Future<?> f = executor.submit(() -> index.indexTools(sessionId, tools));
f.get();
index.clear(sessionId);
Defensive patterns

Strategy: try-catch

Try / catch

// AlreadyClosedException inside add is swallowed by the library with a warning;
// coordinate lifecycle at call site:
synchronized (sessionLock) {
    index.indexTools(sessionId, tools);
}

Prevention

When it happens

Trigger: Calling indexTool/indexTools (which call add) for a sessionId at nearly the same moment another thread calls clear() on that session, closing the writer; retry/replay of indexing after session cleanup.

Common situations: Concurrent tool registration while an HTTP session or agent session is torn down; scheduled indexing jobs racing with user-initiated session clears; multiplexed requests sharing a sessionId whose lifecycle is managed elsewhere.

Understand the failure class

Background: "Invalid state transition" errors: "status must be X, actually Y", "already rejected/charging/uninstalled", "cannot ... while running" — what they mean when a library rejects your call — this error's family across 31 libraries.

Related errors


AI-assisted analysis of spring-projects/spring-ai@98a7beda4f (2026-09-11). Data as JSON: /api/errors/3b3af61db26c5ce4. Report an issue: GitHub.

Appendix: source

Thrown at spring-ai-tool-search-tool/src/main/java/org/springframework/ai/tool/toolsearch/index/lucene/LuceneToolIndex.java:189

	 * {@link #clearIndex(String)} is called concurrently for the same session from a
	 * different thread. The advisor's built-in flow prevents this via the atomic
	 * fingerprint-check in {@code initializeSession()}. Direct callers must ensure that
	 * no concurrent {@code clearIndex()} runs for the same session; if one does, the
	 * document is silently dropped (the session was cleared anyway).
	 * @param sessionId the session ID associated with the tool
	 * @param id unique identifier for the tool
	 * @param toolName name of the tool
	 * @param toolDescription description of the tool (searchable)
	 */
	public void add(String sessionId, String id, String toolName, String toolDescription) {
		try {
			SessionIndex sessionIndex = getOrCreateSessionIndex(sessionId);
			Document doc = this.createDocument(sessionId, id, toolName, toolDescription);
			sessionIndex.writer.addDocument(doc);
		}
		catch (AlreadyClosedException ex) {
			if (logger.isWarnEnabled()) {
				logger.warn("Skipping add for session '" + sessionId + "': index was concurrently cleared");
			}
		}
		catch (IOException e) {
			throw new RuntimeException("Failed to add document to index", e);
		}
	}

	/**
	 * Commits all pending changes to all session indexes. Call this after batch additions
	 * for better performance.
	 */
	public void commit() {
		for (Map.Entry<String, SessionIndex> entry : this.sessionIndexes.entrySet()) {
			try {
				SessionIndex sessionIndex = entry.getValue();
				sessionIndex.writer.commit();
				sessionIndex.refreshReader();
			}

View on GitHub (pinned to 98a7beda4f)