toeverything/AFFiNE · error · ActionForbidden
action_forbidden
action_forbidden
Error message
This feature is temporarily unavailable for you.
What it means
Thrown by WorkspaceMemberResolver.assertCanInviteOrShare when the acting user has been quarantined or banned by AFFiNE's invite-abuse detection system (runtime.isInviteAbuseUserQuarantinedOrBanned). It guards invite-link creation and share actions against accounts flagged for abusive invite behavior. The generic 'temporarily unavailable' message is intentional: it does not disclose that an abuse filter fired. Code action_forbidden, HTTP 403.
Solutions
- Stop retrying — the block is server-side state tied to the account, not a transient failure.
- Contact the workspace/server administrator or AFFiNE support to review and lift the quarantine on the account.
- Use a different, non-flagged account for legitimate invite needs while the review is pending.
- If you operate the server, inspect the invite-abuse quarantine state for the user via the runtime abuse controls and clear it if the flag was a false positive.
Defensive patterns
Strategy: try-catch
Type guard
function isActionForbiddenTemporarilyUnavailable(e: unknown): boolean {
const ext = (e as { extensions?: Record<string, unknown> })?.extensions;
return ext?.type === 'ACTION_FORBIDDEN' &&
String(ext?.message ?? '').includes('temporarily unavailable');
} Try / catch
try {
const link = await createInviteLink(workspaceId, expireTime);
} catch (e) {
if (isActionForbiddenTemporarilyUnavailable(e)) {
// account quarantined/banned by invite-abuse system — surface 'contact support', do NOT retry
return showContactSupportState();
}
throw e;
} Prevention
- Do not auto-retry on 403 action_forbidden — quarantine is server-side state.
- Rate-limit your own invite/send actions well below abuse thresholds.
- Distinguish this from the new-account cooldown by checking account age first.
When it happens
Trigger: A user flagged by the invite-abuse system calls createInviteLink (or any share/invite action routed through assertCanInviteOrShare). The user-level check runs before the workspace-level one, so the flagged actor is blocked on every workspace they touch.
Common situations: A user sent many invites in a short window and tripped the abuse detector; a shared/test account was previously quarantined; spam-like invite patterns from automated scripts reusing one account.
Related errors
- action_forbidden_on_non_team_workspace
- already_in_space
- can_not_revoke_yourself
- no_more_seat
- too_many_request
AI-assisted analysis of toeverything/AFFiNE@2af30773ae (2026-08-18).
Data as JSON: /api/errors/e95baad57277a627.
Report an issue: GitHub.
Appendix: source
Thrown at packages/backend/server/src/core/workspaces/resolvers/member.ts:119
throw new ActionForbidden(
'Workspace names containing links or domains cannot be used to invite members.'
);
}
}
@ResolveField(() => UserType, {
description: 'Owner of workspace',
complexity: 2,
})
async owner(@Parent() workspace: WorkspaceType) {
return this.models.workspaceUser.getOwner(workspace.id);
}
@ResolveField(() => Int, {
description: 'member count of workspace',
complexity: 2,
})
memberCount(@Parent() workspace: WorkspaceType) {
return this.models.workspaceUser.count(workspace.id);
}
@ResolveField(() => [InviteUserType], {
description: 'Members of workspace',
complexity: 2,
})
async members(
@CurrentUser() user: CurrentUser,
@Parent() workspace: WorkspaceType,
@Args('skip', { type: () => Int, nullable: true }) skip?: number,
@Args('take', { type: () => Int, nullable: true }) take?: number,
@Args('query', { type: () => String, nullable: true }) query?: string
) {
await this.ac
.user(user.id)
.workspace(workspace.id)
.assert('Workspace.Users.Read');View on GitHub (pinned to 2af30773ae)