{"record":{"id":"019a1deb3a8c9bcf","repo":"toeverything/AFFiNE","slug":"too-many-request-019a1d","errorCode":"too_many_request","errorMessage":"Too many requests.","messagePattern":"Too many requests\\.","errorType":"exception","errorClass":"TooManyRequest","httpStatus":429,"severity":"error","filePath":"packages/backend/server/src/core/workspaces/abuse.ts","lineNumber":276,"sourceCode":"          ? 'fail_open'\n          : 'fail_closed',\n      });\n      this.logger.error('Workspace invite quota native assert failed', {\n        userId: input.actorUserId,\n        workspaceId: input.workspaceId,\n        targetCount: input.targetCount,\n        targetDomainsSummary: input.targetDomains,\n        sourceTrusted: input.source?.trusted ?? false,\n        country: input.source?.country,\n        asn: input.source?.asn,\n        requestId: input.requestId,\n        cfRay: input.source?.rayId,\n        error: message,\n      });\n      if (this.config.auth.inviteQuotaFailOpenOnRuntimeError) {\n        return { decision: { allowed: true, requested: input.targetCount } };\n      }\n      throw new TooManyRequest();\n    }\n    metrics.workspace\n      .counter('invite_quota_requested_targets')\n      .add(input.targetCount);\n    metrics.workspace\n      .histogram('invite_quota_counter_latency_ms')\n      .record(Date.now() - start);\n\n    if (!decision.allowed) {\n      metrics.workspace.counter('invite_quota_rejected').add(1, {\n        reason: decision.reason ?? 'unknown',\n      });\n      metrics.workspace.counter('invite_quota_reject_by_reason').add(1, {\n        reason: decision.reason ?? 'unknown',\n      });\n      if (this.config.auth.inviteQuotaShadowMode) {\n        this.logger.warn('Workspace invite quota shadow rejected', {\n          userId: input.actorUserId,","sourceCodeStart":258,"sourceCodeEnd":294,"githubUrl":"https://github.com/toeverything/AFFiNE/blob/26c515e050211269e911f7d9cfe162a26c83ed98/packages/backend/server/src/core/workspaces/abuse.ts#L258-L294","documentation":"Surfaced as TooManyRequest (HTTP 429) by InviteQuotaAssertService, but at this exact line it is NOT a real quota decision: it is the fail-closed branch of the catch around the native invite-quota runtime (assertWorkspaceInviteQuotaV1). When the native runtime throws and config.auth.inviteQuotaFailOpenOnRuntimeError is false, the service rejects the invite rather than risk allowing abuse.","triggerScenarios":"The invite-quota native runtime crashes, times out, or returns a non-Error failure while processing a workspace invite, AND the server is configured fail-closed (auth.inviteQuotaFailOpenOnRuntimeError=false). The user sees 429 even though the quota itself was never evaluated.","commonSituations":"The abuse-detection runtime is down or being deployed; a bad deploy of the native module; a network blip between backend and quota runtime; flipping fail-open off during a shared infra outage.","solutions":["Inspect server logs for 'Workspace invite quota native assert failed' and fix the failing runtime dependency.","If you intentionally want invites to proceed during runtime outages, set auth.inviteQuotaFailOpenOnRuntimeError=true (weigh abuse risk).","Retry the invite after the runtime service recovers; surface retry-after guidance to the user.","Verify the runtime provider is reachable and healthy (network, auth, capacity)."],"exampleFix":"// before (config)\nauth: { inviteQuotaFailOpenOnRuntimeError: false }\n// after (opt-in fail-open during runtime outages)\nauth: { inviteQuotaFailOpenOnRuntimeError: true }","handlingStrategy":"retry","validationCode":"// no client input prevents this; preflight the abuse runtime\nconst healthy = await adminRuntimeHealthCheck()\nif (!healthy) deferInviteBatch()","typeGuard":null,"tryCatchPattern":"try {\n  await workspace.inviteMembers(emails)\n} catch (e) {\n  if (e.code === 'too_many_request') { await sleep(backoffMs); await workspace.inviteMembers(emails) }\n  else throw e\n}","preventionTips":["Monitor invite_quota_runtime_fallback metric spikes.","Keep the abuse runtime highly available.","Decide fail-open vs fail-closed deliberately and document it.","Batch invites to reduce call frequency during degraded windows."],"tags":["rate-limit","abuse-detection","invite","resilience","fail-closed"],"backgroundTag":null,"analyzedSha":"26c515e050211269e911f7d9cfe162a26c83ed98","analyzedAt":"2026-08-12T13:15:16.447Z","schemaVersion":2},"datasetVersion":"2026-08-12T23:17:12.415Z"}