alibaba/spring-ai-alibaba · error · IllegalStateException

Thread not found:

Error message

Thread not found: 

What it means

RedisSaver.release() throws IllegalStateException('Thread not found: <name>') when the thread metadata map (THREAD_META_PREFIX+threadName) has no thread_id field. This means release() was called for a threadName that was never created via put() (or whose metadata was deleted/expired), so there is nothing to release.

Source

Thrown at spring-ai-alibaba-graph-core/src/main/java/com/alibaba/cloud/ai/graph/checkpoint/savers/redis/RedisSaver.java:393

			throw new IllegalArgumentException("threadId is not allow null");
		}

		String threadName = threadNameOpt.get();
		RLock lock = redisson.getLock(LOCK_PREFIX + threadName);
		boolean tryLock = false;
		try {
			// 3 seconds timeout for write operations (release) - longer timeout for concurrent scenarios
			tryLock = lock.tryLock(3, TimeUnit.SECONDS);
			if (!tryLock) {
				throw new RuntimeException("Failed to acquire lock for thread: " + threadName);
			}

			String metaKey = THREAD_META_PREFIX + threadName;
			RMap<String, String> meta = redisson.getMap(metaKey);

			String threadId = meta.get(FIELD_THREAD_ID);
			if (threadId == null) {
				throw new IllegalStateException("Thread not found: " + threadName);
			}

			// Mark thread as released
			meta.put(FIELD_IS_RELEASED, "true");

			// Update reverse mapping
			String reverseKey = THREAD_REVERSE_PREFIX + threadId;
			RMap<String, String> reverse = redisson.getMap(reverseKey);
			if (reverse != null) {
				reverse.put(FIELD_IS_RELEASED, "true");
			}

			// Get checkpoints for Tag (using thread_id)
			String contentKey = CHECKPOINT_PREFIX + threadId;
			Collection<Checkpoint> checkpoints = deserializeCheckpoints(contentKey);

			return new Tag(threadName, checkpoints);

View on GitHub (pinned to f82da0b50f)

Solutions

  1. Verify a checkpoint was written first (put() creates the thread meta via getOrCreateThreadId) before calling release()
  2. Check the exact threadName matches the one used in put() (case, prefix)
  3. Confirm the Redisson client points at the expected Redis host/db and the metadata key still exists (TTL settings)
  4. Guard the release with a list()/get() existence check, or catch IllegalStateException and treat as idempotent no-op

Example fix

// before
saver.release(config); // may throw Thread not found
// after
if (!saver.list(config).isEmpty()) {
    saver.release(config);
}
Defensive patterns

Strategy: validation

Validate before calling

if (saver.list(config).isEmpty()) { throw new IllegalStateException("Cannot release thread '" + threadName + "': no checkpoints exist"); }

Type guard

boolean threadExists(RunnableConfig config) { return config.threadId().isPresent() && !saver.list(config).isEmpty(); }

Try / catch

try { saver.release(config); } catch (IllegalStateException e) { if (e.getMessage().startsWith("Thread not found")) { log.warn("Thread {} never created or expired; skipping release", threadName); return; } throw e; }

Prevention

When it happens

Trigger: Calling release() before any put() for that threadName; metadata map evicted by Redis maxmemory policy or TTL expiry; wrong threadName spelling/case; connecting to a different Redis database than the one holding the data.

Common situations: Batch release scripts iterating thread names that never checkpointed; TTL configured on RedisSaver letting metadata expire while clients still reference old thread ids; environment mismatch (dev vs prod Redis).

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


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