toeverything/AFFiNE · error · ActionForbidden
action_forbidden
action_forbidden
Error message
You are not allowed to perform this action.
What it means
Thrown by SubscriptionService.checkout when the server runs with the canary namespace flag in production (env.namespaces.canary && env.prod) and the requesting user's email is not in the staff allowlist (feature.isStaff). During a canary rollout of the payments stack, checkout is restricted to staff.
Source
Thrown at packages/backend/server/src/plugins/payment/service.ts:137
return [
...(await this.userManager.filterPrices(prices, customer)),
...this.workspaceManager.filterPrices(prices, customer),
];
}
async checkout(
params: z.infer<typeof CheckoutParams>,
args: z.infer<typeof CheckoutExtraArgs>
) {
const { plan, recurring, variant } = params;
if (
env.namespaces.canary &&
env.prod &&
args.user &&
!this.feature.isStaff(args.user.email)
) {
throw new ActionForbidden();
}
const manager = this.select(plan);
const result = CheckoutExtraArgs.safeParse(args);
if (!result.success) {
throw new InvalidCheckoutParameters();
}
return manager.checkout(
{
plan,
recurring,
variant: variant ?? null,
},
params,
args
);View on GitHub (pinned to b4c8548c09)
Solutions
- If you operate the server: turn off the canary namespace (env.namespaces.canary) on production once checkout is stable.
- If you must test on canary-prod: use a staff email allowlisted in FeatureService.isStaff.
- If you are an end-user/client: wait for full rollout or use the non-canary endpoint; no client change can bypass the gate.
Example fix
# before (server env) NAMESPACES_CANARY=true # non-staff user checkout -> ActionForbidden # after NAMESPACES_CANARY=false # checkout open to all users
Defensive patterns
Strategy: try-catch
Try / catch
try {
const url = await svc.checkout(params, args);
} catch (e) {
if (gqlErrorCode(e) === 'action_forbidden') {
notify('Checkout is temporarily limited to staff. Try again after rollout.');
return;
}
throw e;
} Prevention
- Operators: disable the canary namespace on production once checkout is validated.
- Test on canary with a staff-allowlisted account only.
- Monitor for action_forbidden rate in prod as a signal the canary gate is still on.
When it happens
Trigger: Any checkout call by a non-staff user while the deployment is canary-enabled production; canary flag left on after testing; staging-like canary environment shared with external beta users.
Common situations: Canary namespace not disabled after a payment migration; real users routed to the canary instance by a load balancer/feature flag; developer testing with a personal (non-staff) account on a canary build.
Related errors
AI-assisted analysis of toeverything/AFFiNE@b4c8548c09 (2026-08-18).
Data as JSON: /api/errors/5d7703bdea6808dd.
Report an issue: GitHub.