{"record":{"id":"4ba1aab659c449bc","repo":"danny-avila/LibreChat","slug":"group-principal-not-found","errorCode":null,"errorMessage":"Group principal not found","messagePattern":"Group principal not found","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"api/server/services/PermissionService.js","lineNumber":46,"sourceCode":"const validateResourceType = (resourceType) => {\n  const validTypes = Object.values(ResourceType);\n  if (!validTypes.includes(resourceType)) {\n    throw new Error(`Invalid resourceType: ${resourceType}. Valid types: ${validTypes.join(', ')}`);\n  }\n};\n\nconst ensureLocalUserPrincipalExists = async (principalId) => {\n  const user = await db.findUser({ _id: principalId }, '_id');\n  if (!user) {\n    throw new Error('User principal not found');\n  }\n  return user._id.toString();\n};\n\nconst ensureLocalGroupPrincipalExists = async (principalId) => {\n  const group = await db.findGroupById(principalId, { _id: 1 });\n  if (!group) {\n    throw new Error('Group principal not found');\n  }\n  return group._id.toString();\n};\n\n/**\n * @import { TPrincipal } from 'librechat-data-provider'\n */\n/**\n * Grant a permission to a principal for a resource using a role\n * @param {Object} params - Parameters for granting role-based permission\n * @param {string} params.principalType - PrincipalType.USER, PrincipalType.GROUP, or PrincipalType.PUBLIC\n * @param {string|mongoose.Types.ObjectId|null} params.principalId - The ID of the principal (null for PrincipalType.PUBLIC)\n * @param {string} params.resourceType - Type of resource (e.g., 'agent')\n * @param {string|mongoose.Types.ObjectId} params.resourceId - The ID of the resource\n * @param {string} params.accessRoleId - The ID of the role (e.g., AccessRoleIds.AGENT_VIEWER, AccessRoleIds.AGENT_EDITOR)\n * @param {string|mongoose.Types.ObjectId} params.grantedBy - User ID granting the permission\n * @param {mongoose.ClientSession} [params.session] - Optional MongoDB session for transactions\n * @returns {Promise<Object>} The created or updated ACL entry","sourceCodeStart":28,"sourceCodeEnd":64,"githubUrl":"https://github.com/danny-avila/LibreChat/blob/5ff282f9006c436e561de1afd39a481bea1ef0d8/api/server/services/PermissionService.js#L28-L64","documentation":"ensureLocalGroupPrincipalExists() in PermissionService.js:46 throws when db.findGroupById(principalId) returns null — the principalId supplied for a GROUP principal does not resolve to an existing local group. Mirrors the user principal check; prevents phantom-group ACL entries.","triggerScenarios":"grantPermission is called with principalType=GROUP and a principalId that is a valid ObjectId but no group with that _id exists. Occurs when sharing with a group that was deleted, or when an Entra-synced group id is used but local group sync has not run.","commonSituations":"Group was deleted between the UI listing it and the grant call. Entra ID groups feature enabled but the group has not yet been mirrored locally. Stale group id cached on the client.","solutions":["Confirm the group still exists (db.findGroupById) before issuing the grant.","If using Entra groups, ensure the Entra-to-local group sync has materialized the group.","Refresh the group list in the UI and reselect if the group was removed."],"exampleFix":null,"handlingStrategy":"validation","validationCode":"async function assertGroupExists(principalId) {\n  const g = await db.findGroupById(principalId, { _id: 1 });\n  if (!g) throw new Error(`no group for ${principalId}`);\n}","typeGuard":null,"tryCatchPattern":null,"preventionTips":["When using Entra groups, ensure local sync has materialized them before granting.","Refresh group lists in the UI before allowing a share action."],"tags":["permissions","acl","groups","validation"],"backgroundTag":null,"analyzedSha":"5ff282f9006c436e561de1afd39a481bea1ef0d8","analyzedAt":"2026-08-12T21:38:08.145Z","schemaVersion":2},"datasetVersion":"2026-08-12T23:17:12.415Z"}