{"record":{"id":"d801567058d8e48f","repo":"alibaba/spring-ai-alibaba","slug":"thread-found-but-belongs-to-a-different-graph-user","errorCode":null,"errorMessage":"Thread found but belongs to a different graph/user.","messagePattern":"Thread found but belongs to a different graph/user\\.","errorType":"http","errorClass":"ResponseStatusException","httpStatus":404,"severity":"error","filePath":"spring-ai-alibaba-studio/src/main/java/com/alibaba/cloud/ai/agent/studio/controller/GraphThreadController.java","lineNumber":96,"sourceCode":"\t\tif (!graphLoader.listGraphs().contains(graphName)) {\n\t\t\tthrow new ResponseStatusException(HttpStatus.NOT_FOUND, \"Graph not found: \" + graphName);\n\t\t}\n\t}\n\n\tprivate Thread findThreadOrThrow(String graphName, String userId, String threadId) {\n\t\tString appName = toAppName(graphName);\n\t\tOptional<Thread> optionalThread =\n\t\t\t\tthreadService.getThread(appName, userId, threadId, Optional.empty()).block();\n\n\t\tif (optionalThread == null || !optionalThread.isPresent()) {\n\t\t\tthrow new ResponseStatusException(HttpStatus.NOT_FOUND,\n\t\t\t\t\tString.format(\"Thread not found: graphName=%s, userId=%s, threadId=%s\",\n\t\t\t\t\t\t\tgraphName, userId, threadId));\n\t\t}\n\n\t\tThread thread = optionalThread.get();\n\t\tif (!Objects.equals(thread.appName(), appName) || !Objects.equals(thread.userId(), userId)) {\n\t\t\tthrow new ResponseStatusException(HttpStatus.NOT_FOUND,\n\t\t\t\t\t\"Thread found but belongs to a different graph/user.\");\n\t\t}\n\t\treturn thread;\n\t}\n\n\t@GetMapping(\"/graphs/{graphName}/users/{userId}/threads/{threadId}\")\n\tpublic Thread getThread(\n\t\t\t@PathVariable String graphName,\n\t\t\t@PathVariable String userId,\n\t\t\t@PathVariable String threadId) {\n\t\tvalidateGraphExists(graphName);\n\t\treturn findThreadOrThrow(graphName, userId, threadId);\n\t}\n\n\t@GetMapping(\"/graphs/{graphName}/users/{userId}/threads\")\n\tpublic List<Thread> listThreads(\n\t\t\t@PathVariable String graphName,\n\t\t\t@PathVariable String userId) {","sourceCodeStart":78,"sourceCodeEnd":114,"githubUrl":"https://github.com/alibaba/spring-ai-alibaba/blob/f82da0b50f35744c13968191be2b1cd2452ef550/spring-ai-alibaba-studio/src/main/java/com/alibaba/cloud/ai/agent/studio/controller/GraphThreadController.java#L78-L114","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","solutions":["Ensure threadIds are globally unique (use generated IDs) and never reused across graphs or users.","Inspect the stored Thread record and correct its appName/userId if data was seeded incorrectly.","Verify the thread service stores/keys threads with the full (appName, userId, threadId) tuple.","Delete the inconsistent record and recreate the thread."],"exampleFix":"// before\ncreateThreadWithId(\"my-graph\", \"u1\", \"shared-thread-id\"); // same id used for another graph\n// after\ncreateThreadWithId(\"my-graph\", \"u1\", UUID.randomUUID().toString());","handlingStrategy":"validation","validationCode":"// ensure unique, non-reused thread ids client-side\nconst threadId = crypto.randomUUID(); // never reuse across graphs/users","typeGuard":null,"tryCatchPattern":"try {\n  await api.get(`/graphs/${graphName}/users/${userId}/threads/${threadId}`);\n} catch (e) {\n  if (e.response?.status === 404 &&\n      e.response.data.includes('different graph/user')) {\n    // id collision or stale data: recreate thread with a fresh UUID\n  }\n}","preventionTips":["Generate threadIds with UUIDs; never reuse across graphs or users.","Do not manually seed thread records with mismatched appName/userId.","Verify the persistence layer scopes lookups by appName and userId."],"tags":["http-404","rest","ownership-mismatch"],"backgroundTag":"record-not-found","analyzedSha":"f82da0b50f35744c13968191be2b1cd2452ef550","analyzedAt":"2026-09-09T15:32:42.421Z","contentChangedAt":"2026-09-09T15:32:42.421Z","schemaVersion":2},"datasetVersion":"2026-09-17T15:17:12.973Z"}