{"record":{"id":"538cb1271068ac1a","repo":"alibaba/spring-ai-alibaba","slug":"thread-already-exists","errorCode":null,"errorMessage":"Thread already exists: ","messagePattern":"Thread already exists: ","errorType":"http","errorClass":"ResponseStatusException","httpStatus":400,"severity":"warning","filePath":"spring-ai-alibaba-studio/src/main/java/com/alibaba/cloud/ai/agent/studio/controller/GraphThreadController.java","lineNumber":139,"sourceCode":"\t\t}\n\n\t\treturn response.threads().stream()\n\t\t\t\t.filter(s -> !s.threadId().startsWith(EVAL_SESSION_ID_PREFIX))\n\t\t\t\t.collect(toList());\n\t}\n\n\t@PostMapping(\"/graphs/{graphName}/users/{userId}/threads/{threadId}\")\n\tpublic Thread createThreadWithId(\n\t\t\t@PathVariable String graphName,\n\t\t\t@PathVariable String userId,\n\t\t\t@PathVariable String threadId,\n\t\t\t@RequestBody(required = false) Map<String, Object> state) {\n\t\tvalidateGraphExists(graphName);\n\t\tString appName = toAppName(graphName);\n\n\t\ttry {\n\t\t\tfindThreadOrThrow(graphName, userId, threadId);\n\t\t\tthrow new ResponseStatusException(HttpStatus.BAD_REQUEST, \"Thread already exists: \" + threadId);\n\t\t}\n\t\tcatch (ResponseStatusException e) {\n\t\t\tif (e.getStatusCode() != HttpStatus.NOT_FOUND) {\n\t\t\t\tthrow e;\n\t\t\t}\n\t\t}\n\n\t\tMap<String, Object> initialState = (state != null) ? state : Collections.emptyMap();\n\t\tThread createdThread = threadService\n\t\t\t\t.createThread(appName, userId, new ConcurrentHashMap<>(initialState), threadId)\n\t\t\t\t.block();\n\n\t\tif (createdThread == null) {\n\t\t\tthrow new ResponseStatusException(HttpStatus.INTERNAL_SERVER_ERROR, \"Failed to create thread\");\n\t\t}\n\t\treturn createdThread;\n\t}\n","sourceCodeStart":121,"sourceCodeEnd":157,"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#L121-L157","documentation":"GraphThreadController.createThreadWithId rejects client-supplied thread IDs that already exist for the given graph and user. It first runs findThreadOrThrow as an existence probe; if the thread IS found, it throws a 400 BAD_REQUEST ResponseStatusException. This enforces thread IDs as client-chosen unique keys per (graph, user) pair.","triggerScenarios":"POST to /graphs/{graphName}/users/{userId}/threads/{threadId} (PUT-style create) with a threadId that was already created previously for the same graph and user; retrying a successful creation with the same ID; two concurrent creations of the same ID.","commonSituations":"Client retries after a network timeout when the first request actually succeeded; a UI generating deterministic thread IDs from session names; scripts re-running a setup step that creates threads with fixed IDs.","solutions":["Check existence first with GET /graphs/{graphName}/users/{userId}/threads/{threadId} and reuse or delete the existing thread","Use the ID-less creation endpoint POST /graphs/{graphName}/users/{userId}/threads and let the service generate a unique threadId","Generate a unique threadId client-side (e.g. UUID) instead of a fixed value","If the existing thread is stale, DELETE it first, then recreate with the same ID","Catch the 400 ResponseStatusException in your client and treat it as idempotent success if the thread content is acceptable"],"exampleFix":"// before\nPOST /graphs/agent/users/u1/threads/session-1   // 400 Thread already exists\n// after\nString threadId = UUID.randomUUID().toString();\nPOST /graphs/agent/users/u1/threads/\" + threadId","handlingStrategy":"validation","validationCode":"// check existence before creating\ntry {\n  restTemplate.getForObject(\"/graphs/{g}/users/{u}/threads/{t}\", Void.class, graph, user, threadId);\n  return; // already exists\n} catch (HttpClientErrorException.NotFound ignored) { }\n// safe to create","typeGuard":null,"tryCatchPattern":"try { create(...) } catch (HttpStatusCodeException e) { if (e.getStatusCode() == HttpStatus.BAD_REQUEST) { /* reuse existing thread */ } else throw e; }","preventionTips":["Prefer generated-ID endpoints over client-chosen IDs","Use UUIDs for client-supplied thread IDs","Make creation retries idempotent by GETting the thread on 400","Clean up fixed-ID test threads between runs"],"tags":["http","duplicate-resource","thread-management"],"backgroundTag":"file-already-exists","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"}