{"record":{"id":"5555bb33144b06ac","repo":"toeverything/AFFiNE","slug":"copilot-quota-exceeded","errorCode":"copilot_quota_exceeded","errorMessage":"You have reached the limit of actions in this workspace, please upgrade your plan.","messagePattern":"You have reached the limit of actions in this workspace, please upgrade your plan\\.","errorType":"exception","errorClass":"CopilotQuotaExceeded","httpStatus":402,"severity":"error","filePath":"packages/backend/server/src/plugins/copilot/conversation/policy.ts","lineNumber":27,"sourceCode":"@Injectable()\nexport class ConversationPolicy {\n  constructor(\n    private readonly models: Models,\n    private readonly quota: QuotaService\n  ) {}\n\n  async getQuota(userId: string) {\n    const quota = await this.quota.getUserQuota(userId);\n    const limit = quota.copilotActionLimit;\n\n    const used = await this.models.copilotSession.countUserMessages(userId);\n\n    return { limit, used };\n  }\n\n  async checkQuota(userId: string) {\n    if (!(await this.hasQuota(userId))) {\n      throw new CopilotQuotaExceeded();\n    }\n  }\n\n  async hasQuota(userId: string) {\n    const { limit, used } = await this.getQuota(userId);\n    return !(limit !== undefined && Number.isFinite(limit) && used >= limit);\n  }\n\n  shouldScheduleTitle(prompt: Pick<ResolvedPrompt, 'action'>) {\n    return !prompt.action;\n  }\n\n  shouldGenerateTitle(input: { title: string | null; turns: Turn[] }) {\n    if (input.title || !input.turns.length) {\n      return false;\n    }\n\n    let hasUser = false;","sourceCodeStart":9,"sourceCodeEnd":45,"githubUrl":"https://github.com/toeverything/AFFiNE/blob/b4c8548c09da21b2898443559a5b846f0ccf5dd8/packages/backend/server/src/plugins/copilot/conversation/policy.ts#L9-L45","documentation":"CopilotQuotaExceeded (code copilot_quota_exceeded) is thrown by CopilotPolicy.checkQuota when the user's consumed message count (models.copilotSession.countUserMessages) has reached quota.copilotActionLimit from their plan. Only finite limits trigger it: limit undefined or Infinity means unlimited. It gates message creation, so the user must upgrade or wait for the limit window to reset.","triggerScenarios":"Calling createMessage (or any flow that runs checkQuota) after used >= limit on a free/capped plan; limits changed on an active user dropping limit below current usage; heavy automated scripting that posts many messages.","commonSituations":"Free-tier users automate copilot calls and hit the cap mid-task; a plan downgrade applies a lower limit than already consumed; QA load tests exhaust a shared account quota.","solutions":["Call getQuota(userId) before sending and stop or prompt for upgrade when used >= limit","Upgrade the user's plan to raise copilotActionLimit","Wait for the quota window reset (usage is counted per period) if the plan is periodic","As an admin: verify quota.copilotActionLimit is configured as intended for the user's plan tier"],"exampleFix":"// before\nawait inbox.createMessage(userId, options); // throws copilot_quota_exceeded\n\n// after\nconst { limit, used } = await policy.getQuota(userId);\nif (limit !== undefined && Number.isFinite(limit) && used >= limit) {\n  return promptUpgradePlan();\n}\nawait inbox.createMessage(userId, options);","handlingStrategy":"validation","validationCode":"const { limit, used } = await policy.getQuota(userId);\nconst hasQuota = !(limit !== undefined && Number.isFinite(limit) && used >= limit);\nif (!hasQuota) {\n  return showUpgradePrompt({ limit, used });\n}","typeGuard":null,"tryCatchPattern":"try {\n  await inbox.createMessage(userId, options);\n} catch (e) {\n  if (e.code === 'copilot_quota_exceeded') {\n    const { limit, used } = await policy.getQuota(userId);\n    showUpgradePrompt({ limit, used });\n  } else throw e;\n}","preventionTips":["Check quota before each send and show remaining count in the UI","Batch/queue drafts locally when near the limit instead of firing failing requests","Gate automated scripts on getQuota so they stop before exhausting the plan"],"tags":["copilot","quota","billing","rate-limit"],"backgroundTag":"quota-exceeded","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"}