{"record":{"id":"af04834dff824ecc","repo":"calcom/cal.diy","slug":"cannot-redirect-to-self","errorCode":null,"errorMessage":"Cannot redirect to self.","messagePattern":"Cannot redirect to self\\.","errorType":"http","errorClass":"BadRequestException","httpStatus":400,"severity":"error","filePath":"apps/api/v2/src/modules/ooo/services/ooo.service.ts","lineNumber":93,"sourceCode":"      if (existingOooRedirect) {\n        throw new BadRequestException(\"Booking redirect infinite not allowed.\");\n      }\n    }\n  }\n\n  async checkDuplicateOOOEntry(userId: number, start?: Date, end?: Date) {\n    if (start && end) {\n      const duplicateEntry = await this.oooRepository.getOooByUserIdAndTime(userId, start, end);\n\n      if (duplicateEntry) {\n        throw new ConflictException(\"Ooo entry already exists.\");\n      }\n    }\n  }\n\n  checkRedirectToSelf(userId: number, toUserId?: number) {\n    if (toUserId && toUserId === userId) {\n      throw new BadRequestException(\"Cannot redirect to self.\");\n    }\n  }\n\n  async checkIsValidOOO(userId: number, ooo: CreateOutOfOfficeEntryDto | UpdateOutOfOfficeEntryDto) {\n    this.isStartBeforeEnd(ooo.start, ooo.end);\n    await this.checkExistingOooRedirect(userId, ooo.start, ooo.end, ooo.toUserId);\n    await this.checkDuplicateOOOEntry(userId, ooo.start, ooo.end);\n    await this.checkRedirectToSelf(userId, ooo.toUserId);\n    await this.checkUserEligibleForRedirect(userId, ooo.toUserId);\n  }\n\n  async createUserOOO(userId: number, body: CreateOutOfOfficeEntryDto) {\n    await this.checkIsValidOOO(userId, body);\n    const { reason, ...rest } = body;\n    const ooo = await this.oooRepository.createUserOOO({\n      ...rest,\n      userId,\n      reasonId: OOO_REASON_TO_REASON_ID[reason ?? OutOfOfficeReason[\"UNSPECIFIED\"]],","sourceCodeStart":75,"sourceCodeEnd":111,"githubUrl":"https://github.com/calcom/cal.diy/blob/176037d0afbe572f870a3c702985e7cd83fe6c0c/apps/api/v2/src/modules/ooo/services/ooo.service.ts#L75-L111","documentation":"BadRequestException thrown by checkRedirectToSelf() when toUserId is provided and equals userId. An OOO entry cannot redirect bookings back to the same user it is created for — that would be a self-loop.","triggerScenarios":"POST/PATCH /v2/ooo where the authenticated user sets toUserId to their own user id; a picker whose default option is the current user; copying a userId from the session into the redirect field.","commonSituations":"UI defaulting the redirect target to 'me'; account-switching where the current user id changes but the form keeps the old toUserId; passing the whole user object's id without filtering self.","solutions":["Exclude the current user from the redirect-target picker list.","Client-side, clear or block submission when toUserId === currentUserId.","If the user genuinely wants no redirect, omit toUserId entirely rather than setting it to self."],"exampleFix":"// before\ntoUserId = currentUser.id; // self\n// after\ntoUserId = teammates.find(t => t.userId !== currentUser.id)?.userId;\n// and omit the field entirely if no other teammate is available","handlingStrategy":"validation","validationCode":"function sanitizeRedirectTarget(currentUserId: number, toUserId?: number): number | undefined {\n  if (toUserId === undefined) return undefined;\n  if (toUserId === currentUserId) throw new Error('Cannot redirect to self');\n  return toUserId;\n}","typeGuard":"const isOtherUser = (currentUserId: number, toUserId?: number): boolean =>\n  toUserId !== undefined && toUserId !== currentUserId;","tryCatchPattern":null,"preventionTips":["Exclude the current user from the redirect picker options.","Clear toUserId if no teammate is selected instead of defaulting to self.","Block submission client-side when toUserId === currentUserId."],"tags":["business-rule","ooo","self-reference","validation","nestjs"],"backgroundTag":null,"analyzedSha":"176037d0afbe572f870a3c702985e7cd83fe6c0c","analyzedAt":"2026-08-12T19:12:41.464Z","schemaVersion":2},"datasetVersion":"2026-08-12T23:17:12.415Z"}