{"record":{"id":"f5d780b953681c82","repo":"toeverything/AFFiNE","slug":"too-many-request-f5d780","errorCode":"too_many_request","errorMessage":"Server is busy","messagePattern":"Server is busy","errorType":"exception","errorClass":"TooManyRequest","httpStatus":429,"severity":"warning","filePath":"packages/backend/server/src/plugins/copilot/resolver.ts","lineNumber":609,"sourceCode":"        ) as ChatMessageType[],\n      })),\n      'updatedAt',\n      pagination,\n      totalCount\n    );\n  }\n\n  private async createCopilotSessionInternal(\n    user: CurrentUser,\n    options: CreateChatSessionInput\n  ): Promise<string> {\n    // permission check based on session type\n    await this.assertPermission(user, options);\n\n    const lockFlag = `${COPILOT_LOCKER}:session:${user.id}:${options.workspaceId}`;\n    await using lock = await this.mutex.acquire(lockFlag);\n    if (!lock) {\n      throw new TooManyRequest('Server is busy');\n    }\n\n    return await this.chatSession.create({\n      ...options,\n      pinned: options.pinned ?? false,\n      docId: options.docId ?? null,\n      userId: user.id,\n    });\n  }\n\n  @Mutation(() => String, {\n    description: 'Create a chat session',\n    deprecationReason: 'use `createCopilotSessionWithHistory` instead',\n  })\n  @CallMetric('ai', 'chat_session_create')\n  async createCopilotSession(\n    @CurrentUser() user: CurrentUser,\n    @Args({ name: 'options', type: () => CreateChatSessionInput })","sourceCodeStart":591,"sourceCodeEnd":627,"githubUrl":"https://github.com/toeverything/AFFiNE/blob/b4c8548c09da21b2898443559a5b846f0ccf5dd8/packages/backend/server/src/plugins/copilot/resolver.ts#L591-L627","documentation":"Thrown by the copilot chat-session create mutation when the per-user, per-workspace mutex (lock key `copilot:session:{userId}:{workspaceId}`) cannot be acquired. AFFiNE serializes all chat-session mutations for one user+workspace so concurrent creates cannot interleave; acquisition is non-blocking, and failure immediately raises TooManyRequest (code `too_many_request`, status `too_many_requests`) with the message 'Server is busy'. It signals lock contention with an in-flight mutation, not global server load.","triggerScenarios":"Calling createCopilotSession / createCopilotSessionWithHistory while another session mutation (create, update, fork, cleanup) for the SAME user.id + workspaceId still holds the lock — e.g. double-clicking 'New chat', firing the mutation from two tabs, or retrying before the first request finished.","commonSituations":"Duplicate form submission in the frontend; React StrictMode double-invoking an effect that sends the mutation; a slow create request still being awaited while the UI or a retry helper fires again; test suites issuing parallel creates for one user/workspace.","solutions":["De-duplicate submissions client-side: disable the button / set an in-flight flag until the first createCopilotSession resolves","Retry once after the in-flight mutation completes — the lock is auto-released (`await using`) when the first request finishes","Verify only one component instance mounts and calls the mutation for the same workspace","If parallel calls are intentional, queue them sequentially instead of firing them concurrently"],"exampleFix":"// before\nawait createCopilotSession({ variables: { options } }); // fired twice on double click\n\n// after\nconst creating = useRef(false);\nasync function handleCreate() {\n  if (creating.current) return; // skip duplicate submit\n  creating.current = true;\n  try {\n    await createCopilotSession({ variables: { options } });\n  } finally {\n    creating.current = false;\n  }\n}","handlingStrategy":"retry","validationCode":"// guard: skip duplicate create while one is in flight\nconst inflightCreate = useRef<Promise<string> | null>(null);\nfunction createSession(options: CreateChatSessionInput) {\n  inflightCreate.current ??= createCopilotSession({ variables: { options } })\n    .finally(() => (inflightCreate.current = null));\n  return inflightCreate.current;\n}","typeGuard":"function isServerBusy(e: unknown): boolean {\n  return (\n    !!e && typeof e === 'object' &&\n    (e as { extensions?: { code?: string } }).extensions?.code === 'too_many_request'\n  );\n}","tryCatchPattern":"try {\n  await createCopilotSession({ variables: { options } });\n} catch (e) {\n  if (isServerBusy(e)) {\n    await waitForInflightMutations(); // lock is released when the other request ends\n    return createCopilotSession({ variables: { options } }); // single retry\n  }\n  throw e;\n}","preventionTips":["One in-flight session mutation per user+workspace in the UI; disable the action while pending","Serialize session mutations through a per-workspace promise queue","Never fire the create mutation from effects that can double-run (StrictMode) without an in-flight guard"],"tags":["copilot","chat-session","concurrency","rate-limit","mutex","graphql"],"backgroundTag":"http-429-too-many-requests","analyzedSha":"b4c8548c09da21b2898443559a5b846f0ccf5dd8","analyzedAt":"2026-08-18T21:16:52.546Z","contentChangedAt":"2026-08-18T21:16:52.546Z","schemaVersion":2},"datasetVersion":"2026-09-14T05:17:10.506Z"}