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
- Check the workspace member quota (memberLimit - memberCount) before inviting and cap the batch accordingly.
- Free seats by removing inactive members, or upgrade the workspace plan / apply a license to raise memberLimit.
- 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
- Check memberLimit - memberCount before bulk invites.
- Remember pending invites count toward seats — base the check on live quota, not the UI list.
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
- action_forbidden
- action_forbidden_on_non_team_workspace
- already_in_space
- too_many_request
- user_not_found
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.memberLimitView on GitHub (pinned to 4953682779)