toeverything/AFFiNE · error · NotFound
not_found
not_found
Error message
Invitation not found
What it means
Thrown by `WorkspaceService.getInviteInfo` when neither a cached link invite (`workspace:inviteLinkId:${inviteId}`) nor a `workspace_user` row by that id exists. The invitation id is unresolvable. Coded under the generic `not_found` (resource_not_found) category with the literal message 'Invitation not found'.
Source
Thrown at packages/backend/server/src/core/workspaces/service.ts:56
private readonly runtime: BackendRuntimeProvider
) {}
async getInviteInfo(inviteId: string): Promise<InviteInfo> {
// invite link
const invite = await this.cache.get<InviteInfo>(
`workspace:inviteLinkId:${inviteId}`
);
if (typeof invite?.workspaceId === 'string') {
return {
...invite,
isLink: true,
};
}
const workspaceUser = await this.models.workspaceUser.getById(inviteId);
if (!workspaceUser) {
throw new NotFound('Invitation not found');
}
return {
isLink: false,
workspaceId: workspaceUser.workspaceId,
inviteeUserId: workspaceUser.userId,
inviterUserId: workspaceUser.inviterId,
};
}
async getWorkspaceInfo(workspaceId: string) {
const workspaceContent = await this.doc.getWorkspaceContent(workspaceId);
let avatar = DEFAULT_WORKSPACE_AVATAR;
if (workspaceContent?.avatarKey) {
const avatarBlob = await this.blobStorage.get(
workspaceId,
workspaceContent.avatarKeyView on GitHub (pinned to 26c515e050)
Solutions
- Request a fresh invitation from the workspace owner/admin.
- Verify the invite id is complete and unmodified (URL-decode it first if from a URL).
- If you operate the cache, confirm Redis is reachable and the invite-link TTL is sane.
- Differentiate link ids from email-invite ids in the client to avoid cross-use.
Example fix
// before
const info = await workspaceService.getInviteInfo(inviteId);
// after
try {
const info = await workspaceService.getInviteInfo(inviteId);
} catch (e) {
if (e.code === 'not_found') {
throw new ClientVisibleError('This invitation no longer exists. Please ask for a new one.');
}
throw e;
} Defensive patterns
Strategy: try-catch
Validate before calling
// Best-effort pre-check via the resolver-level getInviteInfo
const info = await sdk.getInviteInfo({ inviteId }).catch(() => null);
if (!info) {
throw new ClientVisibleError('This invitation no longer exists.');
} Type guard
function isResolvableInvite(invite) {
return Boolean(invite && (invite.isLink || invite.workspaceId));
} Try / catch
try {
const info = await workspaceService.getInviteInfo(inviteId);
} catch (e) {
if (e.code === 'not_found') {
throw new ClientVisibleError('Invitation invalid or expired. Request a new one.');
}
throw e;
} Prevention
- Distinguish link ids from email-invite ids client-side to avoid cross-use.
- URL-decode invite ids before lookup.
- Request fresh invitations when this error fires; do not retry the same id.
When it happens
Trigger: Calling `getInviteInfo(inviteId)` (directly or via the resolver) with an id that is not a link invite in the cache and not a `workspace_user` id. Typoed id, expired/evicted link, revoked email invite, or an id from a different environment.
Common situations: Old link invite whose Redis entry TTL'd; admin deleted the pending invitation row; id was truncated when copied; link from staging used against production; the invitation was already accepted and the row mutated.
Related errors
AI-assisted analysis of toeverything/AFFiNE@26c515e050 (2026-08-12).
Data as JSON: /api/errors/d1a4aba2326a2cf5.
Report an issue: GitHub.