fix(goals): prevent race condition on period reset and fix UTC boundary#345
Merged
Priyanshu-byte-coder merged 1 commit intoMay 19, 2026
Conversation
Two bugs in the GET /api/goals handler caused goal progress to be lost
incorrectly at period boundaries.
Bug 1 - Race condition on period reset:
Promise.all fires all goal-reset DB writes concurrently. When two requests
arrive simultaneously (two browser tabs, mobile + desktop), both read the
same stale period_start, both decide a reset is needed, and both issue
UPDATE current=0. Any progress written between the two reads is silently
zeroed by the second update.
Fixed by adding .lt("period_start", periodStart) to the update so only
one concurrent write wins. The losing request re-fetches the current row
instead of returning a stale or double-zeroed value.
Bug 2 - Local timezone used for period boundary calculation:
getPeriodStart used getDay()/setDate()/setHours() which all operate in
the server's local timezone. On servers not running in UTC the weekly
Monday boundary and monthly first-of-month boundary shift by the UTC
offset, resetting goals a day early or late for affected users.
Fixed by switching to getUTCDay()/setUTCDate()/setUTCHours() and
Date.UTC() so the boundary is always calculated in UTC regardless of
server timezone.
|
@anshul23102 is attempting to deploy a commit to the PRIYANSHU DOSHI's projects Team on Vercel. A member of the Team first needs to authorize it. |
Priyanshu-byte-coder
approved these changes
May 19, 2026
Owner
Priyanshu-byte-coder
left a comment
There was a problem hiding this comment.
Both fixes are correct. The optimistic lock (.lt('period_start', ...)) ensures only one concurrent reset wins — the losing request re-fetches the already-reset row instead of silently zeroing progress. The UTC boundary fix (getUTCDay/setUTCHours) prevents server-timezone drift from shifting Monday to Sunday or Tuesday.
9975624
into
Priyanshu-byte-coder:main
7 checks passed
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Closes #344
What was wrong
Two bugs in `src/app/api/goals/route.ts` caused goal progress to be lost at period boundaries.
Bug 1 - Race condition:
`Promise.all` fires all goal-reset writes concurrently. Two simultaneous GET requests both read the same stale `period_start`, both issue `UPDATE current=0`, and the second write zeros out any progress recorded between the two reads.
Bug 2 - Local timezone boundary:
`getPeriodStart` used local-timezone methods (`getDay`, `setDate`, `setHours`, `Date(year, month, ...)`). On servers not running in UTC the weekly and monthly reset boundaries shift by the UTC offset, triggering resets a day early or late.
What changed
Files changed
Type of change