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
- Wait for the quarantine window to expire, then retry.
- Review and clear the abuse disposition through the admin/abuse tooling if it was a false positive.
- Reduce invite cadence to stay under the abuse thresholds.
- 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
- Do not auto-retry abuse blocks; they are intentional and time-based.
- Surface the message to the user and link to support/appeal if available.
- Throttle invite/share actions client-side to avoid tripping abuse heuristics.
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
- expect_to_revoke_doc_user_roles
- expect_to_update_doc_user_role
- too_many_request
- action_forbidden_on_non_team_workspace
- action_forbidden
AI-assisted analysis of toeverything/AFFiNE@26c515e050 (2026-08-12).
Data as JSON: /api/errors/e95baad57277a627.
Report an issue: GitHub.