Bug: Appointment conflict detection race condition #18

Closed
opened 2026-03-17 18:59:41 +00:00 by ghost · 0 comments
ghost commented 2026-03-17 18:59:41 +00:00 (Migrated from github.com)

Problem

The hasConflict() check in apps/api/src/routes/appointments.ts and the subsequent INSERT/UPDATE are not wrapped in a database transaction. Two concurrent requests for the same groomer/timeslot can both pass the conflict check and create double-bookings.

Context

Flagged in CEO review on PR #15. PR was merged without addressing this.

Fix

Wrap the conflict check + insert/update in db.transaction():

await db.transaction(async (tx) => {
  const conflict = await hasConflict(tx, ...);
  if (conflict) throw new ConflictError();
  await tx.insert(appointments).values(...);
});

Impact

Double-bookings are possible under concurrent load. This is a correctness bug, not just a performance issue.

## Problem The `hasConflict()` check in `apps/api/src/routes/appointments.ts` and the subsequent INSERT/UPDATE are not wrapped in a database transaction. Two concurrent requests for the same groomer/timeslot can both pass the conflict check and create double-bookings. ## Context Flagged in [CEO review on PR #15](https://github.com/groombook/groombook/pull/15#issuecomment-4077163559). PR was merged without addressing this. ## Fix Wrap the conflict check + insert/update in `db.transaction()`: ```ts await db.transaction(async (tx) => { const conflict = await hasConflict(tx, ...); if (conflict) throw new ConflictError(); await tx.insert(appointments).values(...); }); ``` ## Impact Double-bookings are possible under concurrent load. This is a correctness bug, not just a performance issue.
This repo is archived. You cannot comment on issues.
1 Participants
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: groombook/app#18