toeverything/AFFiNE · error · ActionForbidden

action_forbidden

action_forbidden

Error message

This feature is temporarily unavailable for you.

What it means

Thrown by DocResolver.assertCanShare (used by publishDoc) when the runtime flags the acting user as invite-abuse quarantined or banned. The generic message is deliberately vague to avoid revealing anti-abuse state; the server logs the userId/workspaceId/action at warn level. ActionForbidden is an action_forbidden category error.

Source

Thrown at packages/backend/server/src/core/workspaces/resolvers/doc.ts:320

    private readonly ac: PermissionAccess,
    private readonly permission: PermissionService,
    private readonly models: Models,
    private readonly cache: Cache,
    private readonly event: EventBus,
    private readonly config: Config,
    private readonly runtime: BackendRuntimeProvider
  ) {}

  private async assertCanShare(
    userId: string,
    context: { workspaceId: string; docId: string; action: 'publishDoc' }
  ) {
    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.'
      );
    }
    const user = await this.models.user.get(userId);
    const newAccountAgeMs = this.config.auth.newAccountShareActionDelay * 1000;
    if (!user || !canUserExecuteLimitedActions(user, newAccountAgeMs)) {
      this.logger.warn('Share action blocked for new account', {

View on GitHub (pinned to 26c515e050)

Solutions

  1. Contact support/admin to review the abuse flag and lift the quarantine if it is a false positive.
  2. Wait out the quarantine window if it is time-boxed, then retry.
  3. Verify the user is not actually sending bulk invites that trigger the detection.
  4. Operators can adjust the abuse detection thresholds or review the user's abuse state in the runtime provider.
Defensive patterns

Strategy: try-catch

Validate before calling

// No reliable client-side check; abuse state is server-private.
// Best practice: gate share UI behind a lightweight canShare probe query
// that returns the server's verdict without mutating state.

Type guard

function isActionForbidden(e: unknown): boolean {
  return (
    typeof e === 'object' &&
    e !== null &&
    (e as any).extensions?.code === 'action_forbidden'
  );
}

Try / catch

try {
  await gql.publishDoc({ workspaceId, docId, mode });
} catch (e) {
  if (isActionForbidden(e)) {
    notifyUser('Sharing is temporarily unavailable for your account. Contact support.');
    return;
  }
  throw e;
}

Prevention

When it happens

Trigger: Calling publishDoc (or any share action routed through assertCanShare) while runtime.isInviteAbuseUserQuarantinedOrBanned(userId) returns true - the user has been flagged by the invite-abuse detection system.

Common situations: A user sent many invites that were flagged as abuse; account caught in automated abuse quarantine; shared IP/ASN with abusive actors; false positive from aggressive abuse thresholds.

Related errors


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