{"record":{"id":"3cc45ea773ea608d","repo":"alibaba/spring-ai-alibaba","slug":"thread-not-found-appname-s-userid-s-threadid","errorCode":null,"errorMessage":"Thread not found: appName=%s, userId=%s, threadId=%s","messagePattern":"Thread not found: appName=(.+?), userId=(.+?), threadId=(.+?)","errorType":"http","errorClass":"ResponseStatusException","httpStatus":404,"severity":"error","filePath":"spring-ai-alibaba-studio/src/main/java/com/alibaba/cloud/ai/agent/studio/controller/ThreadController.java","lineNumber":80,"sourceCode":"\t/**\n\t * Finds a thread by its identifiers or throws a ResponseStatusException if not found or if\n\t * there's an app/user mismatch.\n\t *\n\t * @param appName The application name.\n\t * @param userId The user ID.\n\t * @param threadId The thread ID.\n\t * @return The found Thread object.\n\t * @throws ResponseStatusException with HttpStatus.NOT_FOUND if the thread doesn't exist or\n\t *     belongs to a different app/user.\n\t */\n\tprivate Thread findThreadOrThrow(String appName, String userId, String threadId) {\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\tlog.warn(\n\t\t\t\t\t\"Thread not found for appName={}, userId={}, threadId={}\", appName, userId, threadId);\n\t\t\tthrow new ResponseStatusException(\n\t\t\t\t\tHttpStatus.NOT_FOUND,\n\t\t\t\t\tString.format(\n\t\t\t\t\t\t\t\"Thread not found: appName=%s, userId=%s, threadId=%s\",\n\t\t\t\t\t\t\tappName, userId, threadId));\n\t\t}\n\n\t\tThread thread = optionalThread.get();\n\n\t\tif (!Objects.equals(thread.appName(), appName) || !Objects.equals(thread.userId(), userId)) {\n\t\t\tlog.warn(\n\t\t\t\t\t\"Thread ID {} found but appName/userId mismatch (Expected: {}/{}, Found: {}/{}) -\"\n\t\t\t\t\t\t\t+ \" Treating as not found.\",\n\t\t\t\t\tthreadId,\n\t\t\t\t\tappName,\n\t\t\t\t\tuserId,\n\t\t\t\t\tthread.appName(),\n\t\t\t\t\tthread.userId());\n","sourceCodeStart":62,"sourceCodeEnd":98,"githubUrl":"https://github.com/alibaba/spring-ai-alibaba/blob/f82da0b50f35744c13968191be2b1cd2452ef550/spring-ai-alibaba-studio/src/main/java/com/alibaba/cloud/ai/agent/studio/controller/ThreadController.java#L62-L98","documentation":"ThreadController.findThreadOrThrow throws 404 NOT_FOUND with appName/userId/threadId detail when threadService.getThread returns no Thread for that triple. This is the standard 'thread does not exist' signal for GET /apps/{appName}/users/{userId}/threads/{threadId} and as the pre-check inside createThreadWithId.","triggerScenarios":"GET a threadId that was never created; referencing a thread after the server restarted with an in-memory (non-persistent) thread store; wrong appName or userId in the path; thread deleted by another request.","commonSituations":"Client caching stale thread IDs across studio restarts; typo in appName/userId path segments; using a thread created under /graphs/... routes with the /apps/... routes backed by a different store; in-memory storage lost on redeploy.","solutions":["Call GET /apps/{appName}/users/{userId}/threads to list existing threads and confirm the exact appName/userId/threadId","Create the thread first via POST /apps/{appName}/users/{userId}/threads/{threadId} before reading it","Configure a persistent thread store (Redis/DB) instead of in-memory if threads must survive restarts","Verify the path variables match the values used at creation time (case-sensitive)","Handle 404 in the client and re-create the thread as part of session recovery"],"exampleFix":"// before\nThread t = GET /apps/myapp/users/u1/threads/old-id; // 404 after restart\n// after\nList<Thread> threads = GET /apps/myapp/users/u1/threads;\nString id = threads.isEmpty() ? createThread().threadId() : threads.get(0).threadId();","handlingStrategy":"validation","validationCode":"// verify thread exists before use\nList<Map> threads = restTemplate.getForObject(\"/apps/{a}/users/{u}/threads\", List.class, app, user);\nboolean exists = threads.stream().anyMatch(t -> threadId.equals(((Map) t).get(\"threadId\")));\nif (!exists) { threadId = createThread(); }","typeGuard":null,"tryCatchPattern":"try { return getThread(id); } catch (HttpClientErrorException.NotFound e) { return createNewThread(); }","preventionTips":["Treat thread IDs as ephemeral across studio restarts unless persistent storage is configured","Always fetch the thread list to validate IDs after redeploys","Keep appName/userId values consistent (exact case) between calls","Recover sessions by re-creating threads on 404"],"tags":["http","not-found","thread-management"],"backgroundTag":"resource-not-found","analyzedSha":"f82da0b50f35744c13968191be2b1cd2452ef550","analyzedAt":"2026-09-09T15:32:42.421Z","contentChangedAt":"2026-09-09T15:32:42.421Z","schemaVersion":2},"datasetVersion":"2026-09-14T05:17:10.506Z"}