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

  1. If you operate the server: turn off the canary namespace (env.namespaces.canary) on production once checkout is stable.
  2. If you must test on canary-prod: use a staff email allowlisted in FeatureService.isStaff.
  3. 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

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.