toeverything/AFFiNE · error · NoMoreSeat

no_more_seat

no_more_seat

Error message

No more seat available in the Space ${spaceId}.

What it means

Produced during inviteByEmail's locked processing loop for non-team workspaces: seat math (quota.memberCount + successfulCandidates.length + 1 > quota.memberLimit) shows adding this candidate would exceed the plan's member limit. The candidate is skipped and NoMoreSeat is written to its InviteResult.error. Code no_more_seat, HTTP 400 semantics; note the running successfulCandidates count means earlier candidates in the same batch may have consumed the last seats.

Solutions

  1. Check the workspace member quota (memberLimit - memberCount) before inviting and cap the batch accordingly.
  2. Free seats by removing inactive members, or upgrade the workspace plan / apply a license to raise memberLimit.
  3. Read results[i].error.name === 'no_more_seat' per row and prompt the owner to upgrade instead of retrying — retrying cannot succeed.

Example fix

// before
await inviteByEmail(ws.id, 10_emails); // 3 seats left -> later rows: no_more_seat

// after
const { memberCount, memberLimit } = await getWorkspaceSeatQuota(ws.id);
const free = memberLimit - memberCount;
if (emails.length > free) {
  await confirm(`Only ${free} seats left — invite first ${free} and upgrade?`);
}
await inviteByEmail(ws.id, emails.slice(0, free));
Defensive patterns

Strategy: fallback

Validate before calling

// pre-check seat availability before a non-team invite batch
const { memberCount, memberLimit } = await getWorkspaceSeatQuota(workspaceId);
const freeSeats = memberLimit - memberCount;
const sendable = emails.slice(0, Math.max(0, freeSeats));
if (emails.length > sendable.length) promptUpgradeFor(emails.length - sendable.length);

Type guard

function isNoMoreSeatResult(x: unknown): boolean {
  const err = (x as { error?: unknown })?.error ?? x; // works for per-item results and thrown errors
  return String((err as { name?: string }).name ?? '').toLowerCase() === 'no_more_seat';
}

Try / catch

const results = await inviteByEmail(workspaceId, emails);
for (const r of results) {
  if (isNoMoreSeatResult(r)) { showUpgradePrompt(); break; } // retrying cannot help
}

Prevention

When it happens

Trigger: Inviting N emails to a free/pro (non-team) workspace that has fewer than N free seats; a batch where the first emails fill the remaining seats and later ones hit this error per-item.

Common situations: Hitting the member limit of a free plan mid-onboarding; inviting a large list without checking the member quota first; seat count drifting from the UI because pending invites also count toward memberCount.

Related errors


AI-assisted analysis of toeverything/AFFiNE@4953682779 (2026-08-18). Data as JSON: /api/errors/907e929ce543442f. Report an issue: GitHub.

Appendix: source

Thrown at packages/backend/server/src/core/workspaces/resolvers/member.ts:286

              email: candidate.normalizedEmail,
              registered: false,
            });
          }

          const existingMember = await this.models.workspaceUser.get(
            workspaceId,
            target.id
          );
          if (existingMember) {
            throw new AlreadyInSpace({ spaceId: workspaceId });
          }

          if (!isTeam) {
            const needMoreSeat =
              quota.memberCount + successfulCandidates.length + 1 >
              quota.memberLimit;
            if (needMoreSeat) {
              throw new NoMoreSeat({ spaceId: workspaceId });
            }
          }

          // no need to check quota, directly go allocating seat path
          if (isTeam) {
            const role = await this.models.workspaceUser.set(
              workspaceId,
              target.id,
              WorkspaceRole.Collaborator,
              {
                status: WorkspaceMemberStatus.AllocatingSeat,
                source: WorkspaceMemberSource.Email,
                inviterId: me.id,
              }
            );
            await this.allocateAvailableTeamSeats(
              workspaceId,
              quota.memberLimit

View on GitHub (pinned to 4953682779)