toeverything/AFFiNE · error · TooManyRequest

too_many_request

too_many_request

Error message

Too many requests.

What it means

Thrown by InviteQuotaAssertService.assertWorkspaceInviteQuota when the external invite-quota runtime (runtime.assertWorkspaceInviteQuotaV1) throws. The catch block is fail-closed by default: unless config.auth.inviteQuotaFailOpenOnRuntimeError is true, the server rethrows TooManyRequest (HTTP 429, code too_many_request) rather than let a broken limiter silently admit invite spam. A genuine quota rejection on the allowed path also surfaces under the same code, so the server log line 'Workspace invite quota native assert failed' is what distinguishes this infra-failure variant.

Solutions

  1. Grep server logs for 'Workspace invite quota native assert failed' — its error: field names the real runtime failure (auth, DNS, timeout)
  2. Fix or provision the quota runtime backend so assertWorkspaceInviteQuotaV1 succeeds
  3. Self-host: set auth.inviteQuotaFailOpenOnRuntimeError=true so an unavailable limiter degrades to allow instead of 429
  4. Retry the invite once the underlying issue is resolved; watch the invite_quota_runtime_fallback metric to confirm the mode
Defensive patterns

Strategy: retry

Try / catch

let lastErr;
for (const delay of [1000, 5000, 15000]) {
  try {
    return await inviteMembers(wsId, emails);
  } catch (e) {
    if (e?.code === 'too_many_request' && serverLogsShowRuntimeFallback()) {
      lastErr = e;
      await sleep(delay); // infra fail-closed, not user abuse — safe to retry
      continue;
    }
    throw e;
  }
}
throw lastErr;

Prevention

When it happens

Trigger: Calling workspace invite mutations while the quota runtime errors: connectivity failure to the abuse-control backend, missing/misconfigured runtime credentials, or a malformed decision payload — with failOpen disabled, every such runtime failure becomes 429 for the inviter.

Common situations: Self-hosted deployments that enabled the invite abuse runtime without provisioning its backend; transient network partitions between app server and quota service; cloud config copied to an environment lacking the cloud infrastructure; misreading this 429 as user-level rate limiting when it is actually fail-closed infra.

Understand the failure class

Related errors


AI-assisted analysis of toeverything/AFFiNE@b6de0ad51b (2026-08-18). Data as JSON: /api/errors/019a1deb3a8c9bcf. Report an issue: GitHub.

Appendix: source

Thrown at packages/backend/server/src/core/workspaces/abuse.ts:290

          ? 'fail_open'
          : 'fail_closed',
      });
      this.logger.error('Workspace invite quota native assert failed', {
        userId: input.actorUserId,
        workspaceId: input.workspaceId,
        targetCount: input.targetCount,
        targetDomainsSummary: input.targetDomains,
        sourceTrusted: input.source?.trusted ?? false,
        country: input.source?.country,
        asn: input.source?.asn,
        requestId: input.requestId,
        cfRay: input.source?.rayId,
        error: message,
      });
      if (this.config.auth.inviteQuotaFailOpenOnRuntimeError) {
        return { decision: { allowed: true, requested: input.targetCount } };
      }
      throw new TooManyRequest();
    }
    metrics.workspace
      .counter('invite_quota_requested_targets')
      .add(input.targetCount);
    metrics.workspace
      .histogram('invite_quota_counter_latency_ms')
      .record(Date.now() - start);

    if (!decision.allowed) {
      metrics.workspace.counter('invite_quota_rejected').add(1, {
        reason: decision.reason ?? 'unknown',
      });
      metrics.workspace.counter('invite_quota_reject_by_reason').add(1, {
        reason: decision.reason ?? 'unknown',
      });
      if (this.config.auth.inviteQuotaShadowMode) {
        this.logger.warn('Workspace invite quota shadow rejected', {
          userId: input.actorUserId,

View on GitHub (pinned to b6de0ad51b)