{"record":{"id":"475c045da004c4a0","repo":"passbolt/passbolt_api","slug":"the-role-does-not-exist-or-deleted","errorCode":null,"errorMessage":"The role does not exist or deleted.","messagePattern":"The role does not exist or deleted\\.","errorType":"http","errorClass":"NotFoundException","httpStatus":404,"severity":"error","filePath":"src/Service/Roles/RolesDeleteService.php","lineNumber":67,"sourceCode":"\n    /**\n     * @param \\App\\Utility\\UserAccessControl $uac UAC object.\n     * @param string $roleId Role identifier to update.\n     * @return void\n     */\n    public function delete(UserAccessControl $uac, string $roleId): void\n    {\n        $uac->assertIsAdmin();\n\n        if (!Validation::uuid($roleId)) {\n            throw new BadRequestException(__('The role identifier is not valid.'));\n        }\n\n        try {\n            /** @var \\App\\Model\\Entity\\Role $role */\n            $role = $this->Roles->find('notDeleted')->where(['id' => $roleId])->firstOrFail();\n        } catch (RecordNotFoundException $e) {\n            throw new NotFoundException(__('The role does not exist or deleted.'), null, $e);\n        }\n\n        $role = $this->softDeleteRole($role, $uac);\n\n        $this->dispatchEvent(self::AFTER_ROLE_DELETE_SUCCESS_EVENT_NAME, [\n            'uac' => $uac,\n            'role' => $role,\n        ]);\n    }\n\n    /**\n     * @param \\App\\Model\\Entity\\Role $role Role entity.\n     * @param \\App\\Utility\\UserAccessControl $uac User Access Control.\n     * @return \\App\\Model\\Entity\\Role\n     */\n    private function softDeleteRole(Role $role, UserAccessControl $uac): Role\n    {\n        $data = [","sourceCodeStart":49,"sourceCodeEnd":85,"githubUrl":"https://github.com/passbolt/passbolt_api/blob/31c1bbc10f32808a607fa9bd81891e898779c0bc/src/Service/Roles/RolesDeleteService.php#L49-L85","documentation":"RolesDeleteService::delete throws this NotFoundException when no non-deleted role matches the given UUID (firstOrFail on the notDeleted finder raises RecordNotFoundException). It means the role was already soft-deleted or never existed.","triggerScenarios":"DELETE /roles/{uuid} for an ID that is not in the roles table, or one whose deleted flag is set (previously soft-deleted role).","commonSituations":"Deleting the same role twice (double-submit or retry after a slow response), stale cached role list referencing an already-removed role, or a typo'd UUID from hand-built requests.","solutions":["Call GET /roles to confirm the role still exists and copy its current ID.","If the role was already deleted, treat the operation as done — no retry needed.","If the ID should exist, check whether it was soft-deleted in the DB (roles.deleted = true).","Ensure client caches of role lists are invalidated after deletions."],"exampleFix":"// before\nDELETE /roles/<already-deleted-id> -> 404 The role does not exist or deleted\n// after\nGET /roles  # verify list, only delete existing IDs\nDELETE /roles/<existing-id> -> 200","handlingStrategy":"try-catch","validationCode":"const roles = await api.listRoles();\nif (!roles.some(r => r.id === roleId)) {\n  console.warn('Role no longer exists; skip delete');\n}","typeGuard":"function roleExists(roles, id) {\n  return Array.isArray(roles) && roles.some(r => r && r.id === id);\n}","tryCatchPattern":"try {\n  await api.deleteRole(roleId);\n} catch (e) {\n  if (e.status === 404 && /does not exist or deleted/i.test(e.message)) {\n    // already deleted or never existed: treat as idempotent success\n  } else throw e;\n}","preventionTips":["Treat 404 on delete as idempotent success in retry logic","Refetch the roles list after any delete before further mutations","Invalidate cached role lists on deletion","Handle double-submits by ignoring 404 on repeated deletes"],"tags":["not-found","roles","soft-delete","passbolt"],"backgroundTag":"record-not-found","analyzedSha":"31c1bbc10f32808a607fa9bd81891e898779c0bc","analyzedAt":"2026-09-17T00:04:38.960Z","contentChangedAt":"2026-09-17T00:04:38.960Z","schemaVersion":2},"datasetVersion":"2026-09-21T04:17:39.646Z"}