koala73/worldmonitor · error · ConvexError
SEAT_CAP_REACHED
SEAT_CAP_REACHED
Error message
SEAT_CAP_REACHED
What it means
Structured ConvexError thrown by inviteSeats when the count of currently active or pending seat grants plus the new (non-duplicate) invites would exceed MAX_SEATS (4). The check runs after the seat lock serializes concurrent calls, so the cap cannot be bypassed by racing invitations.
Solutions
- Remove or revoke existing seats before inviting replacements
- Drop pending invites that are no longer needed to free cap space
- Surface the remaining-seat count in the UI before submission
Defensive patterns
Strategy: validation
When it happens
Trigger: Thrown at convex/payments/businessSeats.ts:211 when the library encounters an invalid state.
Common situations: See trigger scenarios.
AI-assisted analysis of koala73/worldmonitor@7d06c8633d (2026-08-21).
Data as JSON: /api/errors/e9dbf4078ecf6fb3.
Report an issue: GitHub.
Appendix: source
Thrown at convex/payments/businessSeats.ts:207
if (existing.status === "accepted") {
results.push({ email, grantId: existing._id, status: "already_accepted" });
continue;
}
if (existing.status === "pending" && existing.expiresAt > now) {
results.push({ email, grantId: existing._id, status: "already_pending" });
continue;
}
}
newEmails.push(email);
}
const currentCount = await countActiveOrPendingGrants(
ctx,
businessSub.dodoSubscriptionId,
now,
);
if (currentCount + newEmails.length > MAX_SEATS) {
throw new ConvexError({ kind: "SEAT_CAP_REACHED" });
}
for (const email of newEmails) {
const grantId = await ctx.db.insert("businessProGrants", {
businessSubscriptionId: businessSub.dodoSubscriptionId,
ownerUserId: userId,
inviteeEmail: email,
domain: ownerDomain,
status: "pending",
createdAt: now,
expiresAt: now + INVITE_TTL_MS,
});
const token = await signBusinessInviteToken(grantId);
await ctx.scheduler.runAfter(
0,
internal.payments.businessSeats.sendBusinessInviteEmail,
{ inviteeEmail: email, ownerEmail, grantId, token },View on GitHub (pinned to 7d06c8633d)