RunWisp

Comparison · vs Jobber

RunWisp vs Jobber: same itch, different reach.

Both replace cron with a Go binary. One stops at scheduled jobs; the other adds services, a web dashboard, and notifications that don't require sendmail.

Quick start GitHub

Should you switch?

The 10-second version. Skim, decide, scroll down for the receipts.

Switch to RunWisp if

  • You want run history you can browse in a web UI, not just grep in syslog.
  • Notifications should hit Slack or Telegram, not depend on a local sendmail.
  • You also supervise long-running services on the same box.
  • Overlap policies and catch-up after downtime matter.
  • One daemon per system beats one daemon per user.
  • You need the same binary on macOS, WSL, and Docker.

Stay on Jobber if

  • Per-user isolation is a hard requirement (multi-tenant box, each user owns their jobs).
  • You want the lightest possible footprint: no embedded SQLite, no web server.
  • Your notification story is already sendmail and you don't want to change it.
  • One YAML file at ~/.jobber and three error policies is all the model you want.

Yes / no, at a glance

Yes / no, both columns win on something.

Feature Jobber RunWisp
Run history per firing
~ jobber log, syslog
SQLite rows + Web UI
Captures stdout / stderr
Retries with backoff
~ Backoff on error only
constant / linear / exp
Failure notifications
~ sendmail or program
Slack / Telegram / email
Coalesced alerts (no notify storms)
Overlap policy
skip / queue / kill
Catch-up after downtime
latest / all / skip
DST handling
deduped / next valid
Web UI / dashboard
TUI over SSH
Long-running services
[services.*]
Per-user daemon isolation
one daemon per user
system-wide
Six-field cron (seconds)
optional seconds field
Single static Go binary
macOS / WSL / Docker
~ Linux + macOS

Pros & cons

What Jobber still does better

  • Per-user daemon model: each Unix user owns their jobs with no shared config or shared daemon.
  • Lighter footprint: no embedded SQLite, no web server, no TUI. Just runs jobs.
  • Simple mental model: one YAML file at ~/.jobber, three error policies, done.
  • Longer track record in the cron-replacement niche.

What RunWisp adds

  • Every firing is a row with status, duration, exit code, and captured stdout/stderr.
  • Web dashboard and TUI for browsing run history without SSH + grep.
  • Six-field cron with an optional seconds field — Jobber's leading column carries over unchanged.
  • Slack, Telegram, Discord, email, and webhook notifications without requiring a local MTA.
  • Overlap policies (skip / queue / kill) are a config field, not your problem.
  • catch_up = "latest" replays missed firings after a reboot.
  • Services (always-on processes) and tasks (scheduled) in one daemon.
  • DST fall-back fires once (deduped), spring-forward fires at the next valid minute.
  • One binary on Linux, macOS, WSL, and Docker. Same TOML everywhere.

The simple case

Nightly backup at 03:00. Both are a few lines in a config file.

.jobber (YAML)
.jobber (YAML)
# ~/.jobber
[jobs]
- name: nightly-backup
  cmd: /usr/local/bin/backup.sh
  time: '0 0 3 * * *'
  onError: Stop
runwisp.toml
runwisp.toml
# runwisp.toml
[tasks.nightly-backup]
cron = "0 3 * * *"
run  = "/usr/local/bin/backup.sh"

Production-grade

Same backup, hardened. notifyProgram scripts on the left; named fields and built-in notifications on the right.

.jobber (production)
.jobber (production)
# ~/.jobber, hardened for production
[jobs]
- name: nightly-backup
  cmd: /usr/local/bin/backup.sh
  time: '0 0 3 * * *'
  onError: Backoff
  notifyOnError:
    - type: program
      path: /usr/local/bin/notify-slack.sh
  notifyOnFailure:
    - type: program
      path: /usr/local/bin/page-ops.sh
runwisp.toml (production)
runwisp.toml (production)
# runwisp.toml, hardened for production
[tasks.nightly-backup]
description       = "Snapshot the data volume to off-site storage"
cron              = "0 3 * * *"
on_overlap        = "skip"            # never two backups at once
timeout           = "55m"             # die before the next firing
retry_attempts    = 2
retry_delay       = "30s"
retry_backoff     = "exponential"
keep_runs         = 60                # two months of history
notify_on_failure = ["slack-ops"]
run               = "/usr/local/bin/backup.sh"

Migration cheat sheet

Jobber concept on the left, RunWisp equivalent on the right.

Jobber RunWisp
~/.jobber (per-user YAML) runwisp.toml (system-wide TOML)
time: '0 0 3 * * *' (6 fields) cron = "0 0 3 * * *" (6-field accepted) or drop the seconds column
cmd: run =
onError: Stop retry_attempts = 0 (default)
onError: Backoff retry_attempts = N + retry_backoff = "exponential"
onError: Continue Default behaviour; failures are logged, next firing proceeds.
notifyOnError: [{type: program}] notify_on_failure = ["slack-ops"]
jobber log Web UI run list / runwisp tui
jobber reload runwisp reload (validates first, no restart)
No overlap handling on_overlap = "skip"
No catch-up after reboot catch_up = "latest" (default)

Gotchas

FAQ

More comparisons: RunWisp vs cron · RunWisp vs systemd timers · RunWisp vs supervisord .

Or see what RunWisp is in one page →

Outgrown ~/.jobber? One binary, full history.

Install RunWisp, translate your YAML jobs to TOML stanzas, and watch every firing land in the web dashboard.