alibaba/spring-ai-alibaba · error · ResponseStatusException

Thread found but belongs to a different app/user.

Error message

Thread found but belongs to a different app/user.

What it means

findThreadOrThrow also throws 404 NOT_FOUND ('Thread found but belongs to a different app/user.') when a Thread record with the given threadId exists but its stored appName or userId differs from the path variables. Threads are namespaced by (appName, userId, threadId), so a match on ID alone is deliberately treated as not found to avoid cross-tenant leakage.

Source

Thrown at spring-ai-alibaba-studio/src/main/java/com/alibaba/cloud/ai/agent/studio/controller/ThreadController.java:99

					HttpStatus.NOT_FOUND,
					String.format(
							"Thread not found: appName=%s, userId=%s, threadId=%s",
							appName, userId, threadId));
		}

		Thread thread = optionalThread.get();

		if (!Objects.equals(thread.appName(), appName) || !Objects.equals(thread.userId(), userId)) {
			log.warn(
					"Thread ID {} found but appName/userId mismatch (Expected: {}/{}, Found: {}/{}) -"
							+ " Treating as not found.",
					threadId,
					appName,
					userId,
					thread.appName(),
					thread.userId());

			throw new ResponseStatusException(
					HttpStatus.NOT_FOUND, "Thread found but belongs to a different app/user.");
		}
		log.debug("Found thread: {}", threadId);
		return thread;
	}

	/**
	 * Retrieves a specific thread by its ID.
	 *
	 * @param appName The application name.
	 * @param userId The user ID.
	 * @param threadId The thread ID.
	 * @return The requested Thread object.
	 * @throws ResponseStatusException if the thread is not found.
	 */
	@GetMapping("/apps/{appName}/users/{userId}/threads/{threadId}")
	public Thread getThread(
			@PathVariable String appName, @PathVariable String userId, @PathVariable String threadId) {

View on GitHub (pinned to f82da0b50f)

Solutions

  1. Use the exact appName and userId that were used when the thread was created
  2. Make client-generated thread IDs unique per user (append userId or use a UUID)
  3. List the user's threads (GET /apps/{appName}/users/{userId}/threads) to find the correct threadId
  4. Create a new thread under the correct (appName, userId) pair instead of reusing the ID
  5. Check for proxy/environment mixing that points the client at another deployment's data

Example fix

// before
String threadId = "main-session"; // same for all users
// after
String threadId = userId + "-main-session"; // namespaced per user
Defensive patterns

Strategy: validation

Validate before calling

// ensure thread belongs to this user before fetching
String scopedId = userId + "-" + sessionId;
try { GET "/apps/{a}/users/{u}/threads/" + scopedId; } catch (NotFound e) { createThread(scopedId); }

Try / catch

try { return getThread(app, user, id); } catch (HttpClientErrorException.NotFound e) { /* wrong owner or missing: create under this user */ return createThread(app, user, id); }

Prevention

When it happens

Trigger: Requesting the same threadId under a different userId or appName than the one that created it; reusing a client-generated threadId (e.g. hardcoded or session-derived) across users; copy-pasting a URL between environments or accounts.

Common situations: Multi-user apps reusing the same session identifier for every user; load balancer routing to a different studio instance where another user created the same ID; tests hardcoding threadId while varying userId.

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/8fe0745ed1c17459. Report an issue: GitHub.