Skip to content
use-cases / on-call-shift-cron / hero
CRON · EXPIRES_AT · NOTIFICATIONS

An on-call escalation that ages out with your shift

Post a cron entry on Monday with expires_at set to Friday 09:00. The job pages your phone every 30 minutes for four days, then deletes itself the moment the shift ends. The next on-call posts theirs. No PagerDuty schedule, no shared scheduler config, no calendar reminder to disable it.

Read the cron docs
use-cases / on-call-shift-cron / handoff

How the shift hands off without a meeting

Three points in the week. The cron entry's existence tracks the shift exactly — no overlap, no gap, no leftover crontab line.

Monday 09:00 → Friday 09:01expires_at carries the shift
MONDAY 09:00

Post the entry

One curl with schedule */30, command pointed at hoody-notifications/push/me, and expires_at = Friday 09:00:00Z. Server returns id e7d3.

MON–FRI

Pings ride your phone

Every 30 minutes for four days the entry runs and pings hoody-notifications. Only your device. The team channel stays quiet.

FRIDAY 09:01

Entry removes itself

At 09:00:00Z the cron service deletes e7d3. The 09:30 tick has nothing to fire. The next on-call has already posted theirs.

Each on-call writes one POST when their shift starts. The expires_at field is the entire handoff protocol — the cron service does the cleanup, on the second.

use-cases / on-call-shift-cron / mechanism

Two curls — one per shift

The whole rotation protocol is two HTTP calls per week. The on-call posts on Monday, lists on Friday, sees the entry is already gone. There is no shared schedule file to merge into.

monday.sh · m.dossantos
POST · entry
# monday 09:00 — i'm on call until friday 09:00
curl -X POST \
  https://oncall.containers.hoody.com/users/me/entries \
  -H "Content-Type: application/json" \
  -d '["schedule":"*/30 * * * *","command":"curl -fsS hoody-notifications/push/m.dossantos","comment":"on-call wk19","expires_at":"2026-05-08T09:00:00Z"]'

# response
HTTP/1.1 201 Created
{ "id":"e7d3", "expires_at":"2026-05-08T09:00:00Z", "enabled":true }
friday.sh · m.dossantos
GET · audit
# friday 09:01 — the next on-call took over
curl GET https://oncall.containers.hoody.com/users/me/entries

HTTP/1.1 200 OK
[
  // my entry e7d3 is gone — it expired
  // at 09:00 sharp. j.okafor's new
  // entry took over at 09:00:30.
]
# no slack thread, no calendar reminder

No shared scheduler service is involved. The cron entry is owned by the engineer; nothing about the next on-call's setup depends on the previous one cleaning up.

use-cases / on-call-shift-cron / powers

Three things this shape removes from your week

An on-call entry that owns its own lifetime stops three classes of mistakes that PagerDuty configs and calendar reminders cannot.

ROUTING

Pings ride one device — yours

Because the entry's command points at your personal notify endpoint, escalations route to your phone for the duration of your shift, and only that. No accidental team-channel spam at 3am.

OWNERSHIP

No shared config to edit, no PR to merge

There is no escalation_policy.yaml that everyone touches. Each engineer owns their entry. Two on-calls in different time zones can't conflict by editing the same file.

CLEANUP

The handoff is in the data, not the chat

When Friday 09:00 rolls over you don't ask 'wait, am I still getting these?' The entry is already gone. Verifying the handoff is one GET that returns one fewer row.

use-cases / on-call-shift-cron / capacity

What the cron service guarantees

Numbers come from the Hoody Cron API. Real limits, not invented ones.

  1. EXPIRY PRECISION1 sec

    Auto-expiration runs against the system clock. A 09:00:00Z expires_at deletes within the same minute the cron tick fires — no 5-minute janitor lag.

  2. FIELDS PER SCHEDULE5

    Standard 5-field cron expressions plus @hourly / @daily / @weekly macros. */30 * * * * is what fires every 30 minutes during your shift.

  3. ISOLATION1 / user

    Each system user has their own crontab. The next on-call's entry lives under their own /users/[name]/entries — never touching yours.

Limits per the Hoody Cron API: managed entries are JSON CRUD with UUIDs and expires_at; raw crontab access available per user; per-user crontab isolation is built in.

use-cases / on-call-shift-cron / punchline

When the shift ends, so does the cron entry — automatically.

before · the rotation fileafter · two curls per week
WHAT THE OLD HANDOFF LOOKED LIKEedit oncall.yaml → PR → merge → page someone to disablefour humans · one shared file · still got pinged at 11am friday
WHAT IT LOOKS LIKE NOWPOST /entries [ expires_at: '2026-05-08T09:00:00Z' ]one engineer · one curl · the cron service does the cleanup
Read the cron docs
use-cases / on-call-shift-cron / replaces

What this replaces

The standard reach-for-it tools when you want on-call rotations. Each one charges you a service, a config repo, and a handoff ritual. A cron entry with expires_at charges you one POST.

  • PagerDuty escalation policiesPer-seat pricing for what is a curl into your phone
  • Opsgenie schedule rotationsA whole product to figure out who gets pinged this week
  • custom on-call config reposShared YAML that everyone PRs and nobody owns
  • remember to disable cron when handing offA calendar reminder that you set, ignore, and resent on Friday
  • manual escalation cleanupSlack thread at 11am Friday — "hey can someone unsubscribe me"
  • VictorOps rotationsAnother scheduler service for what one expires_at field does
use-cases / on-call-shift-cron / cta

Stop disabling crontab lines on Friday morning. Set expires_at on Monday and forget it.

Read the cron docs
use-cases / on-call-shift-cron / related

Read the others