{"record":{"id":"fbcbae6306d02aab","repo":"toeverything/AFFiNE","slug":"cannot-remove-the-last-doc-owner-grant","errorCode":null,"errorMessage":"Cannot remove the last doc owner grant.","messagePattern":"Cannot remove the last doc owner grant\\.","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"packages/backend/server/src/models/permission-write.ts","lineNumber":646,"sourceCode":"      where: {\n        workspaceId,\n        docId,\n        principalType: 'user',\n        principalId: userId,\n        role: 'owner',\n      },\n    });\n    const otherOwners = await this.db.docGrant.count({\n      where: {\n        workspaceId,\n        docId,\n        principalType: 'user',\n        principalId: { not: userId },\n        role: 'owner',\n      },\n    });\n    if (deletingOwner > 0 && otherOwners === 0) {\n      throw new Error('Cannot remove the last doc owner grant.');\n    }\n\n    return await this.db.docGrant.deleteMany({\n      where: {\n        workspaceId,\n        docId,\n        principalType: 'user',\n        principalId: userId,\n      },\n    });\n  }\n}\n","sourceCodeStart":628,"sourceCodeEnd":659,"githubUrl":"https://github.com/toeverything/AFFiNE/blob/26c515e050211269e911f7d9cfe162a26c83ed98/packages/backend/server/src/models/permission-write.ts#L628-L659","documentation":"Thrown by DocPermissionModel.delete (permission-write.ts:646) when removing a user's grant would leave the doc with zero user-owner grants. Mirrors the workspace-owner invariant at the doc level: a doc must always retain at least one owner grant. Guarded by a SELECT ... FOR UPDATE on owner rows. Plain Error.","triggerScenarios":"Revoking the only remaining doc owner's grant via DocPermissionModel.delete; deleting the sole owner's doc grant; cascading a member removal that drops the last owner grant.","commonSituations":"Owner tries to leave a doc they alone own; a cleanup script removes all grants on a doc; ownership transfer was attempted through a code path that didn't first add a new owner.","solutions":["Transfer doc ownership to another user first (grantUserRole with DocRole.Owner), then revoke the old owner.","If the doc should no longer exist, delete the doc rather than removing its last owner grant.","Before deleting, ensure at least one other user owner grant exists."],"exampleFix":"// before\nawait models.docPermission.delete(wsId, docId, ownerUserId);\n// after\nawait models.docPermission.grantUserRole(wsId, docId, otherUserId, DocRole.Owner);\nawait models.docPermission.delete(wsId, docId, ownerUserId);","handlingStrategy":"validation","validationCode":"async function safeRemoveDocOwner(models, wsId: string, docId: string, userId: string) {\n  const otherOwners = await models.db.docGrant.count({\n    where: { workspaceId: wsId, docId, principalType: 'user', principalId: { not: userId }, role: 'owner' },\n  });\n  if (otherOwners === 0) {\n    throw new Error('Refusing to remove the last doc owner; transfer ownership first.');\n  }\n  return models.docPermission.delete(wsId, docId, userId);\n}","typeGuard":null,"tryCatchPattern":null,"preventionTips":["Ensure another user owner grant exists before removing the last one.","Transfer doc ownership before revoking the current owner.","Delete the doc rather than stripping its last owner when the doc is no longer needed."],"tags":["doc","permissions","ownership","invariant"],"backgroundTag":null,"analyzedSha":"26c515e050211269e911f7d9cfe162a26c83ed98","analyzedAt":"2026-08-12T13:15:16.447Z","schemaVersion":2},"datasetVersion":"2026-08-12T23:17:12.415Z"}