toeverything/AFFiNE · error · CannotDeleteAccountWithOwnedTeamWorkspace
cannot_delete_account_with_owned_team_workspace
cannot_delete_account_with_owned_team_workspace
Error message
Cannot delete account. You are the owner of one or more team workspaces. Please transfer ownership or delete them first.
What it means
UserModel.delete() refuses to delete an account that still owns at least one team workspace. It iterates ownedWorkspaces(id) and throws CannotDeleteAccountWithOwnedTeamWorkspace as soon as one is a team workspace — before user.preDelete is emitted or any invitation rows are removed, so nothing is partially deleted.
Solutions
- Transfer ownership of each owned team workspace to another active member (the transferOwner/setOwner flow), then retry deletion.
- Or delete the team workspaces entirely if they are no longer needed.
- List the user's owned workspaces first so you know exactly which ones block deletion.
Example fix
// before
await userModel.delete(userId); // throws if a team workspace is owned
// after
for (const ws of await userModel.ownedWorkspaces(userId)) {
if (await models.workspace.isTeamWorkspace(ws.workspaceId)) {
await workspaceUser.transferOwner(ws.workspaceId, newOwnerId, 'admin');
}
}
await userModel.delete(userId); Defensive patterns
Strategy: validation
Validate before calling
const owned = await userModel.ownedWorkspaces(userId);
const blocking = [];
for (const ws of owned) {
if (await models.workspace.isTeamWorkspace(ws.workspaceId)) blocking.push(ws);
}
if (blocking.length) {
// prompt user to transfer ownership or delete these workspaces first
} Try / catch
try {
await userModel.delete(userId);
} catch (e) {
if (e instanceof CannotDeleteAccountWithOwnedTeamWorkspace) {
// list owned team workspaces and guide the transfer flow
} else throw e;
} Prevention
- Surface owned-team-workspace warnings on the delete-account screen before the user confirms.
- Keep an admin runbook for transferring workspace ownership.
- Never assume delete() is partial — it aborts atomically, so fix ownership and retry.
When it happens
Trigger: Calling the account deletion API for a user who owns one or more workspaces where workspaceModel.isTeamWorkspace(workspaceId) returns true.
Common situations: Self-service 'delete my account' while still owning a shared team workspace; admin bulk cleanup of users who created team workspaces; ownership left on an auto-created workspace after a plan upgrade to team.
Related errors
- new_owner_is_not_active_member
- authentication_required
- copilot_session_not_found
- copilot_session_not_found
- doc_action_denied
AI-assisted analysis of toeverything/AFFiNE@2af30773ae (2026-08-18).
Data as JSON: /api/errors/17e78a9c13a405db.
Report an issue: GitHub.
Appendix: source
Thrown at packages/backend/server/src/models/user.ts:192
return user;
}
async ownedWorkspaces(id: string) {
return await this.models.workspaceUser.getUserActiveRoles(id, {
role: WorkspaceRole.Owner,
});
}
async delete(id: string) {
const ownedWorkspaces = await this.ownedWorkspaces(id);
for (const ws of ownedWorkspaces) {
const isTeamWorkspace = await this.models.workspace.isTeamWorkspace(
ws.workspaceId
);
if (isTeamWorkspace) {
throw new CannotDeleteAccountWithOwnedTeamWorkspace();
}
}
await this.event.emitAsync('user.preDelete', { id });
await this.db.workspaceInvitation.deleteMany({
where: { inviteeUserId: id },
});
const user = await this.db.user.delete({ where: { id } });
this.event.emit('user.deleted', user);
return user;
}
async recreateForBan(id: string) {
// ban an user barely share the same logic with delete an user,
// but keep the record with `disabled` flag
// we delete the account and create it again to trigger all cleanupsView on GitHub (pinned to 2af30773ae)