Spike.sh postmortem on on-call schedules not rotating for gaps

On-call schedules not rotating on gap-based schedules

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…

Damanpreet avatar

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 startA

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 🙂

Discover more from Spike's blog

Subscribe now to keep reading and get access to the full archive.

Continue reading