toeverything/AFFiNE · warning · ActionForbidden

action_forbidden

action_forbidden

Error message

This feature is temporarily unavailable for you.

What it means

Thrown by assertCanInviteOrShare when the acting user is flagged by the invite-abuse system as quarantined or banned (runtime.isInviteAbuseUserQuarantinedOrBanned returns true). The native runtime tracks abuse signals (burst invites, suspicious domains); once a user is in quarantine, all share/invite-link actions are blocked until the disposition expires or is cleared. Reported as action_forbidden (HTTP 403).

Source

Thrown at packages/backend/server/src/core/workspaces/resolvers/member.ts:124

    private readonly quota: QuotaService,
    private readonly config: Config,
    private readonly inviteQuota: InviteQuotaAssertService,
    private readonly runtime: BackendRuntimeProvider
  ) {}

  private async assertCanInviteOrShare(
    userId: string,
    context: {
      workspaceId: string;
      action: 'createInviteLink';
    }
  ) {
    if (await this.runtime.isInviteAbuseUserQuarantinedOrBanned(userId)) {
      this.logger.warn('Share action blocked for quarantined actor', {
        userId,
        ...context,
      });
      throw new ActionForbidden(
        'This feature is temporarily unavailable for you.'
      );
    }
    if (
      await this.runtime.isInviteAbuseWorkspaceQuarantined(context.workspaceId)
    ) {
      this.logger.warn('Share action blocked for quarantined workspace', {
        userId,
        ...context,
      });
      throw new ActionForbidden(
        'This feature is temporarily unavailable for you.'
      );
    }
    // Member invites are owned by native quota; this guard stays for invite links until share/link actions migrate.
    const user = await this.models.user.get(userId);
    const newAccountAgeMs = this.config.auth.newAccountShareActionDelay * 1000;
    if (!user || !canUserExecuteLimitedActions(user, newAccountAgeMs)) {

View on GitHub (pinned to 26c515e050)

Solutions

  1. Wait for the quarantine window to expire, then retry.
  2. Review and clear the abuse disposition through the admin/abuse tooling if it was a false positive.
  3. Reduce invite cadence to stay under the abuse thresholds.
  4. Confirm the flagged userId with the abuse dashboard to rule out account compromise.
Defensive patterns

Strategy: try-catch

Try / catch

try {
  await createInviteLink(workspaceId);
} catch (e) {
  if (isGraphQLError(e, 'action_forbidden') && /temporarily unavailable/i.test(e.message)) {
    // user quarantined/banned: surface a 'try again later' message; do not auto-retry
    showUserBlockedNotice(e);
  } else throw e;
}

Prevention

When it happens

Trigger: Any action routed through assertCanInviteOrShare (e.g. createInviteLink) by a user whose id is in the invite-abuse quarantine or ban set.

Common situations: A user who sent a large burst of invites tripped the abuse heuristic and was auto-quarantined; test/seeding accounts sharing an IP or fingerprint got flagged; a shared VPN/proxy IP caused false positives.

Related errors


AI-assisted analysis of toeverything/AFFiNE@26c515e050 (2026-08-12). Data as JSON: /api/errors/e95baad57277a627. Report an issue: GitHub.