Skip to content

Comments

fix: round-robin reserve per host capacity#24520

Open
husniabad wants to merge 3 commits intocalcom:mainfrom
husniabad:fix/round-robin-host-capacity
Open

fix: round-robin reserve per host capacity#24520
husniabad wants to merge 3 commits intocalcom:mainfrom
husniabad:fix/round-robin-host-capacity

Conversation

@husniabad
Copy link
Contributor

What does this PR do?

Fixes Round-Robin slot capacity calculation to allow multiple bookings per time slot until all hosts are booked. Changes reservation filtering logic to only block slots when reservationsForSlot.length >= usersWithCredentials.length and adds availableHosts field to display remaining capacity.

Visual Demo (For contributors especially)

A visual demonstration is strongly recommended, for both the original and new change (video / image - any one).

Video Demo (if applicable):

  • Show screen recordings of the issue or feature.
  • Demonstrate how to reproduce the issue, the behavior before and after the change.

Image Demo (if applicable):

  • Add side-by-side screenshots of the original and updated change.
  • Highlight any significant change(s).

Mandatory Tasks (DO NOT REMOVE)

  • I have self-reviewed the code (A decent size PR without self-review might be rejected).
  • I have updated the developer docs in /docs if this PR makes changes that would require a documentation change. If N/A, write N/A here and check the checkbox.
  • I confirm automated tests are in place that prove my fix is effective or that my feature works.

How should this be tested?

  • Create 2 (or even 1) Round-Robin event types with same 2 hosts each
  • Navigate to booking page and select any time slot
  • Make first reservation for that slot
  • Verify slot remains available with availableHosts: 1
  • Make second reservation
  • Verify slot disappears only after both hosts are booked
  • Test with different browser/incognito to confirm availability

Expected: Round-Robin slots show available host count and remain bookable until capacity reached


- Are there environment variables that should be set? - What are the minimal test data to have? - What is expected (happy path) to have (input and output)? - Any other important info that could help to test that PR

Checklist

  • I haven't read the contributing guide
  • My code doesn't follow the style guidelines of this project
  • I haven't commented my code, particularly in hard-to-understand areas
  • I haven't checked if my changes generate no new warnings

@vercel
Copy link

vercel bot commented Oct 16, 2025

@husniabad is attempting to deploy a commit to the cal Team on Vercel.

A member of the Team first needs to authorize it.

@github-actions github-actions bot added teams area: teams, round robin, collective, managed event-types 🐛 bug Something isn't working labels Oct 16, 2025
@coderabbitai
Copy link
Contributor

coderabbitai bot commented Oct 16, 2025

Walkthrough

This PR implements capacity-based slot reservation logic for Round-Robin scheduling types. Changes include modifying PrismaSelectedSlotRepository to include generic Round-Robin reservations (userId: -1) in slot queries, updating the reserveSlot handler to detect overbooking by counting total reservations against host capacity, enhancing getAvailableSlots utility with Redis caching and capacity-aware availability logic, adding an availableHosts metric to slot payloads, expanding test coverage with Round-Robin capacity-based scenarios, and updating the SelectedSlot DTO to include the userId field.

Possibly related PRs

  • calcom/cal.com#22705: Introduces PrismaSelectedSlotRepository and SelectedSlot DTO structures that are being extended with userId field and modified query logic in this PR.
  • calcom/cal.com#22787: Adds Redis-backed caching infrastructure to getAvailableSlots, the same utility being enhanced here with composite cache keys and TTL-based storage.
  • calcom/cal.com#23222: Modifies round-robin reservation validation logic to handle generic aggregate reservations (userId -1), directly related to the capacity counting approach introduced here.

Pre-merge checks and finishing touches

❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. You can run @coderabbitai generate docstrings to improve docstring coverage.
✅ Passed checks (4 passed)
Check name Status Explanation
Title Check ✅ Passed The title "fix: round-robin reserve per host capacity" is concise, clear, and directly summarizes the main change—it addresses the core issue of fixing Round-Robin slot capacity calculation to allow multiple bookings per time slot up to host capacity. The title is specific enough to convey the primary purpose of the changeset without being vague or misleading, and it aligns well with the actual code modifications across the repository and test files.
Linked Issues Check ✅ Passed The code changes directly address all primary objectives from the linked issues (#24519 and CAL-6592): tests validate Round-Robin capacity-based reservations, the reservation handler implements capacity checks by counting reservations against host count, the repository query now includes generic Round-Robin reservations (userId: -1), and the slot utility logic implements capacity-aware filtering that blocks slots only when reservations reach host capacity while exposing an availableHosts field. The implementation correctly restores expected behavior where slots remain bookable until all hosts are reserved and allows multiple reservations per time slot based on host capacity.
Out of Scope Changes Check ✅ Passed All changes are directly scoped to implementing Round-Robin capacity-based reservation logic: test additions validate the new behavior, repository and handler modifications support generic Round-Robin reservations with capacity checks, the type definition expansion exposes userId information necessary for Round-Robin logic, and the slot utility enhancements implement capacity-aware availability filtering with availableHosts computation. There are no unrelated refactorings, formatting changes without purpose, or modifications to unrelated features—each change contributes directly to fixing the Round-Robin capacity issue described in the linked issues.
Description Check ✅ Passed The pull request description clearly relates to the changeset by explaining what the PR does (fixing Round-Robin slot capacity calculation), referencing the linked issues (#24519 and CAL-6592), providing manual testing instructions, and outlining the core fix (allowing multiple bookings per time slot until all hosts are booked with proper availableHosts field exposure). The description demonstrates understanding of the problem and the solution being implemented in the code changes.
✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@husniabad husniabad marked this pull request as ready for review October 16, 2025 19:34
@husniabad husniabad requested a review from a team as a code owner October 16, 2025 19:34
@graphite-app graphite-app bot added the community Created by Linear-GitHub Sync label Oct 16, 2025
@graphite-app graphite-app bot requested a review from a team October 16, 2025 19:34
@dosubot dosubot bot added the bookings area: bookings, availability, timezones, double booking label Oct 16, 2025
Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
packages/trpc/server/routers/viewer/slots/util.ts (1)

167-178: Filter Round-Robin reservations to the relevant host pool before counting.

Because findManyUnexpiredSlots now returns every userId: -1 row, slotsSelectedByOtherUsers ends up holding reservations from unrelated Round-Robin events. The later capacity checks (reservationsForSlot.length >= usersWithCredentials.length and the availableHosts math) then treat those foreign holds as ours, so a different team’s reservation at the same timestamp makes our slot look full.

Once the repository query is fixed, ensure this filter only keeps reservations tied to this event type’s host pool (e.g. match eventTypeId or whatever discriminator you choose). Without that, availability for independent RR events collapses.

📜 Review details

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Disabled knowledge base sources:

  • Linear integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between 34a9872 and 7628392.

📒 Files selected for processing (5)
  • apps/web/test/lib/getSchedule/selectedSlots.test.ts (1 hunks)
  • packages/lib/server/repository/PrismaSelectedSlotRepository.ts (1 hunks)
  • packages/lib/server/repository/dto/SelectedSlot.ts (1 hunks)
  • packages/trpc/server/routers/viewer/slots/reserveSlot.handler.ts (1 hunks)
  • packages/trpc/server/routers/viewer/slots/util.ts (6 hunks)
🧰 Additional context used
📓 Path-based instructions (4)
**/*Repository.ts

📄 CodeRabbit inference engine (.cursor/rules/review.mdc)

Repository files must include Repository suffix, prefix with technology if applicable (e.g., PrismaAppRepository.ts), and use PascalCase matching the exported class

Files:

  • packages/lib/server/repository/PrismaSelectedSlotRepository.ts
**/*.ts

📄 CodeRabbit inference engine (.cursor/rules/review.mdc)

**/*.ts: For Prisma queries, only select data you need; never use include, always use select
Ensure the credential.key field is never returned from tRPC endpoints or APIs

Files:

  • packages/lib/server/repository/PrismaSelectedSlotRepository.ts
  • packages/trpc/server/routers/viewer/slots/reserveSlot.handler.ts
  • packages/lib/server/repository/dto/SelectedSlot.ts
  • apps/web/test/lib/getSchedule/selectedSlots.test.ts
  • packages/trpc/server/routers/viewer/slots/util.ts
**/*.{ts,tsx}

📄 CodeRabbit inference engine (.cursor/rules/review.mdc)

Flag excessive Day.js use in performance-critical code; prefer native Date or Day.js .utc() in hot paths like loops

Files:

  • packages/lib/server/repository/PrismaSelectedSlotRepository.ts
  • packages/trpc/server/routers/viewer/slots/reserveSlot.handler.ts
  • packages/lib/server/repository/dto/SelectedSlot.ts
  • apps/web/test/lib/getSchedule/selectedSlots.test.ts
  • packages/trpc/server/routers/viewer/slots/util.ts
**/*.{ts,tsx,js,jsx}

⚙️ CodeRabbit configuration file

Flag default exports and encourage named exports. Named exports provide better tree-shaking, easier refactoring, and clearer imports. Exempt main components like pages, layouts, and components that serve as the primary export of a module.

Files:

  • packages/lib/server/repository/PrismaSelectedSlotRepository.ts
  • packages/trpc/server/routers/viewer/slots/reserveSlot.handler.ts
  • packages/lib/server/repository/dto/SelectedSlot.ts
  • apps/web/test/lib/getSchedule/selectedSlots.test.ts
  • packages/trpc/server/routers/viewer/slots/util.ts
🧠 Learnings (1)
📓 Common learnings
Learnt from: CarinaWolli
PR: calcom/cal.com#22296
File: packages/lib/bookings/filterHostsBySameRoundRobinHost.ts:41-42
Timestamp: 2025-07-22T11:42:47.623Z
Learning: The filterHostsBySameRoundRobinHost function in packages/lib/bookings/filterHostsBySameRoundRobinHost.ts has a known limitation where it doesn't work correctly with fixed hosts or round robin groups. This is pre-existing technical debt that was already broken before the round robin groups feature. CarinaWolli has documented this in Linear issue CAL-6134 for future fix.
🧬 Code graph analysis (1)
apps/web/test/lib/getSchedule/selectedSlots.test.ts (1)
apps/web/test/utils/bookingScenario/bookingScenario.ts (4)
  • TestData (1276-1548)
  • getGoogleCalendarCredential (1229-1237)
  • createBookingScenario (1015-1046)
  • mockCalendarToHaveNoBusySlots (2013-2026)

Comment on lines +63 to +66
OR: [
{ userId: { in: userIds } },
{ userId: -1 } // Include generic Round-Robin reservations
],
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

Do not pull every Round-Robin hold into unrelated availability checks.

Adding { userId: -1 } without scoping by event type makes this query return all Round-Robin reservations across the entire table. Downstream consumers (e.g. _getReservedSlotsAndCleanupExpired) now assume every returned row belongs to the current event type, so any other Round-Robin event that reserves the same timestamp instantly reduces capacity (or blocks bookings) for events whose hosts are completely unrelated. This regresses isolated teams: a booking for Team A at 10:00 with hosts {1,2} will prevent Team B with hosts {10,11} from reserving that same 10:00 slot because the global count trips the capacity guard.

We need to keep the fix (one reservation per slot) while still isolating capacity per host group. At minimum the query must restrict the userId: -1 branch to the relevant eventTypeId(s) so unrelated pools stay independent; otherwise every Round-Robin event competes for the same shared counter. Please tighten this filter (or carry eventTypeId context into the repository) before shipping.

🤖 Prompt for AI Agents
In packages/lib/server/repository/PrismaSelectedSlotRepository.ts around lines
63-66 the OR branch currently includes a bare { userId: -1 } which pulls every
Round-Robin reservation globally; narrow this by adding the relevant eventTypeId
condition so the Round-Robin branch only matches rows for the current event
type(s) (e.g. { AND: [{ userId: -1 }, { eventTypeId: currentEventTypeId }] }).
If the repository method does not yet accept eventTypeId, add it to the method
signature and pass it from callers (or otherwise supply the eventTypeId context)
so the query can include the eventTypeId filter while preserving the "one
reservation per slot" behavior.

Comment on lines +107 to +123
const totalReservations = await prisma.selectedSlots.count({
where: {
slotUtcStartDate,
slotUtcEndDate,
userId: -1,
releaseAt: { gt: new Date() },
},
});

if (totalReservations > eventType.users.length) {
// Rollback: Delete the reservation we just created
await prisma.selectedSlots.delete({
where: { selectedSlotUnique: { userId: -1, slotUtcStartDate, slotUtcEndDate, uid } },
});
throw new TRPCError({
message: "Slot is fully booked",
code: "BAD_REQUEST",
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

Round-Robin overbooking check needs to isolate by event type.

selectedSlots.count currently sums every Round-Robin hold (userId: -1) at the same timestamp, regardless of which event type created it. That means once some other team has two holds at 10:00, your team’s first hold immediately pushes totalReservations above eventType.users.length and throws “Slot is fully booked”, even though none of your hosts are taken. The repository change mirrors this and both together make unrelated Round-Robin events block each other globally.

Please scope the count to the relevant event type (or another discriminator tied to the actual host pool) so we only compare against the capacity that belongs to this event type’s hosts. Right now this breaks RR booking for any org running multiple independent RR event types.

🤖 Prompt for AI Agents
In packages/trpc/server/routers/viewer/slots/reserveSlot.handler.ts around lines
107-123, the selectedSlots.count and subsequent rollback delete are currently
counting all Round-Robin holds (userId: -1) at the same timestamp across event
types; restrict the count to this event type’s host pool by adding the
event-type discriminator to the where clause (e.g., eventTypeId or
eventType.uid) so you only compare holds belonging to this event type, and
include the same discriminator in the delete where to ensure the rollback
targets the exact reservation row you created.

@github-actions
Copy link
Contributor

This PR has been marked as stale due to inactivity. If you're still working on it or need any help, please let us know or update the PR to keep it active.

@github-actions github-actions bot added the Stale label Oct 27, 2025
@github-actions
Copy link
Contributor

This PR has been closed due to inactivity. Please feel free to reopen it if you'd like to continue the work.

@github-actions github-actions bot closed this Nov 10, 2025
@dhairyashiil dhairyashiil reopened this Nov 15, 2025
@github-actions github-actions bot added the Medium priority Created by Linear-GitHub Sync label Nov 15, 2025
Copy link
Member

@dhairyashiil dhairyashiil left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please attach before and after loom video, also please update the issue with video as well 🙏🏼

Copy link
Contributor

@cubic-dev-ai cubic-dev-ai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

2 issues found across 5 files

Prompt for AI agents (all 2 issues)

Understand the root cause of the following 2 issues and fix them.


<file name="packages/trpc/server/routers/viewer/slots/reserveSlot.handler.ts">

<violation number="1" location="packages/trpc/server/routers/viewer/slots/reserveSlot.handler.ts:107">
Round-robin capacity counting is not scoped by eventTypeId, so reservations from other event types with the same time range will prematurely exhaust capacity for this event type.</violation>
</file>

<file name="packages/trpc/server/routers/viewer/slots/util.ts">

<violation number="1" location="packages/trpc/server/routers/viewer/slots/util.ts:129">
Rule violated: **Avoid Logging Sensitive Information**

The new cache miss/hit warnings log the cacheKey string, which embeds the bookerClientUid cookie value. Logging this unique identifier exposes sensitive user data and violates the Avoid Logging Sensitive Information rule.</violation>
</file>

Reply to cubic to teach it or ask questions. Re-run a review with @cubic-dev-ai review this PR

});

// Check capacity after creation to handle race conditions
const totalReservations = await prisma.selectedSlots.count({
Copy link
Contributor

@cubic-dev-ai cubic-dev-ai bot Nov 15, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Round-robin capacity counting is not scoped by eventTypeId, so reservations from other event types with the same time range will prematurely exhaust capacity for this event type.

Prompt for AI agents
Address the following comment on packages/trpc/server/routers/viewer/slots/reserveSlot.handler.ts at line 107:

<comment>Round-robin capacity counting is not scoped by eventTypeId, so reservations from other event types with the same time range will prematurely exhaust capacity for this event type.</comment>

<file context>
@@ -76,35 +76,85 @@ export const reserveSlotHandler = async ({ ctx, input }: ReserveSlotOptions) =&gt;
+        });
+
+        // Check capacity after creation to handle race conditions
+        const totalReservations = await prisma.selectedSlots.count({
+          where: {
+            slotUtcStartDate,
</file context>
Fix with Cubic

return JSON.parse(cached);
}
} catch (error) {
log.warn('Failed to get cached slots result', { error, cacheKey });
Copy link
Contributor

@cubic-dev-ai cubic-dev-ai bot Nov 15, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Rule violated: Avoid Logging Sensitive Information

The new cache miss/hit warnings log the cacheKey string, which embeds the bookerClientUid cookie value. Logging this unique identifier exposes sensitive user data and violates the Avoid Logging Sensitive Information rule.

Prompt for AI agents
Address the following comment on packages/trpc/server/routers/viewer/slots/util.ts at line 129:

<comment>The new cache miss/hit warnings log the cacheKey string, which embeds the bookerClientUid cookie value. Logging this unique identifier exposes sensitive user data and violates the Avoid Logging Sensitive Information rule.</comment>

<file context>
@@ -114,36 +115,28 @@ function withSlotsCache(
+        return JSON.parse(cached);
       }
+    } catch (error) {
+      log.warn(&#39;Failed to get cached slots result&#39;, { error, cacheKey });
     }
-
</file context>
Fix with Cubic

@github-actions
Copy link
Contributor

github-actions bot commented Jan 9, 2026

Devin AI is resolving merge conflicts

This PR has merge conflicts with the main branch. A Devin session has been created to automatically resolve them.

View Devin Session

Devin will:

  1. Merge the latest main into this branch
  2. Resolve any conflicts intelligently
  3. Run lint/type checks to ensure validity
  4. Push the resolved changes

If you prefer to resolve conflicts manually, you can close the Devin session and handle it yourself.

@github-actions
Copy link
Contributor

Devin AI is resolving merge conflicts

This PR has merge conflicts with the main branch. A Devin session has been created to automatically resolve them.

View Devin Session

Devin will:

  1. Merge the latest main into this branch
  2. Resolve any conflicts intelligently
  3. Run lint/type checks to ensure validity
  4. Push the resolved changes

If you prefer to resolve conflicts manually, you can close the Devin session and handle it yourself.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

bookings area: bookings, availability, timezones, double booking 🐛 bug Something isn't working community Created by Linear-GitHub Sync devin-conflict-resolution Medium priority Created by Linear-GitHub Sync size/L teams area: teams, round robin, collective, managed event-types

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Round-Robin Reservation Locks Slot for All Hosts

3 participants