alibaba/spring-ai-alibaba · error · ResponseStatusException

Thread found but belongs to a different graph/user.

Error message

Thread found but belongs to a different graph/user.

What it means

After fetching the thread, findThreadOrThrow verifies that the returned record's appName and userId match the request. A mismatch means the store returned a thread whose owner differs from the requested graph/user, and a 404 NOT_FOUND with this fixed message is thrown to avoid leaking other users' threads.

Solutions

  1. Ensure threadIds are globally unique (use generated IDs) and never reused across graphs or users.
  2. Inspect the stored Thread record and correct its appName/userId if data was seeded incorrectly.
  3. Verify the thread service stores/keys threads with the full (appName, userId, threadId) tuple.
  4. Delete the inconsistent record and recreate the thread.

Example fix

// before
createThreadWithId("my-graph", "u1", "shared-thread-id"); // same id used for another graph
// after
createThreadWithId("my-graph", "u1", UUID.randomUUID().toString());
Defensive patterns

Strategy: validation

Validate before calling

// ensure unique, non-reused thread ids client-side
const threadId = crypto.randomUUID(); // never reuse across graphs/users

Try / catch

try {
  await api.get(`/graphs/${graphName}/users/${userId}/threads/${threadId}`);
} catch (e) {
  if (e.response?.status === 404 &&
      e.response.data.includes('different graph/user')) {
    // id collision or stale data: recreate thread with a fresh UUID
  }
}

Prevention

When it happens

Trigger: A persistence backend returns a Thread record for the threadId whose appName/userId fields differ from the derived GRAPH_APP_PREFIX+graphName or the requested userId — typically caused by ID collisions or inconsistent keys in the store.

Common situations: Reusing threadIds across graphs or users; manually seeded/edited persistence data with mismatched appName; a backend not scoping keys by appName/userId causing cross-tenant lookups.

Understand the failure class

Background: Record Not Found Errors: "not found", RecordNotFound, and "was not found" — what they mean and how to fix them — this error's family across 28 libraries.

Related errors


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

Appendix: source

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

		if (!graphLoader.listGraphs().contains(graphName)) {
			throw new ResponseStatusException(HttpStatus.NOT_FOUND, "Graph not found: " + graphName);
		}
	}

	private Thread findThreadOrThrow(String graphName, String userId, String threadId) {
		String appName = toAppName(graphName);
		Optional<Thread> optionalThread =
				threadService.getThread(appName, userId, threadId, Optional.empty()).block();

		if (optionalThread == null || !optionalThread.isPresent()) {
			throw new ResponseStatusException(HttpStatus.NOT_FOUND,
					String.format("Thread not found: graphName=%s, userId=%s, threadId=%s",
							graphName, userId, threadId));
		}

		Thread thread = optionalThread.get();
		if (!Objects.equals(thread.appName(), appName) || !Objects.equals(thread.userId(), userId)) {
			throw new ResponseStatusException(HttpStatus.NOT_FOUND,
					"Thread found but belongs to a different graph/user.");
		}
		return thread;
	}

	@GetMapping("/graphs/{graphName}/users/{userId}/threads/{threadId}")
	public Thread getThread(
			@PathVariable String graphName,
			@PathVariable String userId,
			@PathVariable String threadId) {
		validateGraphExists(graphName);
		return findThreadOrThrow(graphName, userId, threadId);
	}

	@GetMapping("/graphs/{graphName}/users/{userId}/threads")
	public List<Thread> listThreads(
			@PathVariable String graphName,
			@PathVariable String userId) {

View on GitHub (pinned to f82da0b50f)