On March 25, 2026, a user reported that their on-call schedule was not rotating correctly. The person who had just finished their shift remained shown as on-call, even though the next shift hadn’t started yet and nobody was supposed to be on-call during that gap. We identified the root cause, deployed a fix, and on-call schedules now rotate correctly for all schedule types.
Summary
Some teams configure on-call schedules with intentional gaps — for example, a schedule where someone is on-call only at night, with no coverage during the day. When one shift ended and the next shift hadn’t started yet, the outgoing person should have been marked off-call. Instead, they remained active.
The result: the previous on-call person kept receiving alerts during a period they weren’t supposed to be on-call, and the schedule appeared stuck.
Impact
Customers with gap-based schedules were affected. The previous on-call person continued receiving alerts during periods when no one was supposed to be on-call, and the schedule appeared to not be rotating.
Timeline UTC, March 25, 2026
- 12:25 PM A user reports via live chat that their on-call schedule appears stuck, the same person is shown as on-call across a gap where no one should be on-call.
- 1:50 PM Our team begins investigation into the on-call scheduling engine.
- ~2:15 PM Root cause identified: a code change pushed two weeks prior introduced a condition that skipped ending the current shift if the same person was to be on-call next.
- 2:31 PM Fix deployed. On-call schedules now rotate correctly, including for gap-based configurations.
Root cause
Two weeks ago, we shipped a change to avoid sending redundant shift-end notifications when the same person’s shift was simply continuing. The intent was: if Person A’s shift ends and A is immediately starting the next shift, don’t notify them that they’ve gone off-call and back on-call.
The implementation checked whether the user was changing before ending the current shift. This worked correctly when transitioning between two different people but it had a blind spot: when a shift ends and no new shift is starting (i.e., a gap in the schedule), there’s no “next user” to compare against. The condition was never satisfied, so the shift was never ended.
if (currentShiftToBeEnded) { userIsChanging = currentUser != nextUser if (userIsChanging) { // <-- root cause endShift(currentShiftToBeEnded) }}
The fix: always end the current shift when it’s due to end, regardless of who (if anyone) is coming on next. Shift-end and shift-start are now treated as independent events.
if (currentShiftToBeEnded) { endShift(currentShiftToBeEnded)}
Lessons learned
Gap schedules weren’t covered enough in our test cases. The change was tested against standard rotation scenariosbut not against schedules with intentional gaps i.e. A → Gap → A. We’re adding explicit test coverage for gap-based, night-only, and partial-day schedule types.
On-call rotation test cases
- A → A
- A → B
- A → gap → B
- A → gap → A
- A → end later
- scheduled start → A
Shift lifecycle events should be independent. Ending a shift and starting a new one are two separate concerns. Coupling them — even with good intent — creates fragile logic. The on-call engine should treat each lifecycle event atomically.
Conclusion
On-call schedules with gaps between shifts now rotate correctly. If you’re using a schedule where nobody is on-call during certain hours, your rotations will work as configured.
Thanks for reading. We continue on our mission to build a very flexible on-call for everyone 🙂
