ActionBoxBlog

SREPlatformBackend

Cron job approval workflow: gate scheduled automation before it acts

A copy-paste cron job approval workflow that gates cleanup, backup, and rotation tasks before they run, with explicit timeout and audit behavior.

A cron job that deletes old logs doesn't email you before it runs. It just runs — usually at 3am, usually with rm -rf, and usually on the day your backup script stopped writing. Scheduled automation is the highest-leverage place for a human decision: it's unattended, it repeats, and when it's wrong it's wrong repeatedly.

This post turns any cron job into a gated job: it runs, checks its trigger condition, and asks before the destructive or expensive part executes.

Cron job approval workflow: gate the action, not the job

bash
# Every hour, check disk usage; only ask when it matters
0 * * * * /opt/actionbox/bin/actionbox ask "Nightly backup disk is 94% full — resize volume?" \
  --option resize="Resize volume" \
  --option ignore="Ignore for now" \
  --callback-url https://ops.acme.com/actionbox/disk-callback

The cron job itself never performs the destructive step — it requests a decision and the callback does the work on approval. No polling, no state file, no partial shell scripts.

Step 1: decide, don't notify

A "disk 94% full" notification is noise. A decision with a default action is work. The difference:

  • Notify: "FYI disk is 94% full" → read, ignore, repeat tomorrow
  • Decide: "Approve resizing the volume?" → approve/reject in one tap, or let the callback auto-run the default
bash
0 * * * * actionbox ask "Nightly backup disk is 94% full?" \
  --option resize="Resize volume" \
  --option ignore="Ignore for now" \
  --callback-url https://ops.acme.com/actionbox/disk-callback

For an unattended default, add a generated future RFC3339 value with --expires and pair it with --on-expire-json using a single_choice response. The expiration must be generated at runtime rather than copied as a stale date.

Step 2: rotate with sign-off

Secret rotation is the cron job most teams are afraid to automate — exactly why it needs a gate. Rotate automatically, but stage the production swap behind a decision:

bash
0 3 1 * * actionbox ask "Rotate DATABASE_URL in production?" \
  --option rotate="Rotate now" \
  --option defer="Defer 7 days" \
  --context-json '[{"type":"key_value","items":{"secret":"DATABASE_URL","age_days":"89","rotation_policy":"90d","staging_tested":"true"}}]' \
  --callback-url https://ops.acme.com/actionbox/rotate-callback

The callback is HMAC-signed; your rotation handler verifies it, swaps the secret, and notifies the channel. If the decision times out, nothing rotates — fail-closed by default.

Step 3: backup jobs with a human abort

Backups should run unattended — but give the on-call engineer a veto window:

bash
15 1 * * * actionbox ask "Run nightly pg_dump to S3?" \
  --option run="Run backup" \
  --option hold="Hold (suspected disk issue)" \
  --context-json '[{"type":"key_value","items":{"db":"orders","size_est":"412GB","last_backup":"2026-08-13"}}]' \
  --callback-url https://ops.acme.com/actionbox/backup-callback

If the engineer suspects problems, they tap "Hold" and the backup job skips — instead of paging at 4am because the dump wrote to a failing disk.

Full example: disk-pressure resolver

bash
#!/usr/bin/env bash
# /etc/cron.d/disk-resolver — run hourly
set -euo pipefail

THRESHOLD=90
USAGE=$(df -h / | awk 'NR==2 {gsub(/%/,"",$5); print $5}')

if (( USAGE < THRESHOLD )); then
  exit 0  # under threshold: no gate, no noise
fi

/opt/actionbox/bin/actionbox ask \
  "Root volume is ${USAGE}% full — resize?" \
  --option resize="Resize to 200GB" \
  --option ignore="Watch for now" \
  --context-json "[{\"type\":\"key_value\",\"items\":{\"volume\":\"/\",\"usage\":\"${USAGE}%\",\"host\":\"$(hostname)\"}}]" \
  --callback-url https://ops.acme.com/actionbox/disk-callback

Why gated cron beats "on-call pages"

  • Fail-closed: no answer means no action — the audit trail records the non-decision
  • Explicit defaults: unattended jobs resolve to a named default with a reason
  • One tool for every job: backups, rotations, cleanups, cache refreshes — same ask pattern, same dashboard, same audit log
  • The dashboard is the on-call console: every pending decision with its SLA countdown, no separate notification channel to build

Try it

  1. Install the CLI on any host: curl -fsSL https://actionbox.cloud/install.sh | sh
  2. Create a Source → ACTIONBOX_TOKEN
  3. Replace one echo "disk full" cron notification with an ask

Create a free Source · Cron + CLI reference · Secret rotation with sign-off

Try this workflow in minutes

Create a free Source, then run the exact commands from this post against the live API — no approval infrastructure to build.

S
Suson Sapkota

Founder, Actionbox