[defaults]
[defaults] is the one section in runwisp.toml that doesn’t describe
something to run. Instead it sets fallback values that every
[tasks.*] and [services.*] inherits, unless that task or service
sets the field itself.
It’s here to save you from repeating yourself. Typing
keep_for = "30d" and log_max_size = "100mb" onto twenty different
tasks gets old fast — so put them in [defaults] once, and only bother
overriding the few tasks that actually need a different value.
Available keys
Section titled “Available keys”[defaults]timeout = "1h"log_max_size = "100mb"log_on_full = "drop_old"keep_runs = 50keep_for = "30d"healthy_after = "60s"| Key | Default | What it does |
|---|---|---|
timeout | (unset) | Default per-attempt wall-clock cap. Unset means no timeout. Applies to both tasks and services. |
jitter | (unset) | Default jitter window for cron tasks; off when unset. Every cron task inherits it and joins the daemon-wide one-at-a-time gate — one line to smooth your whole schedule. Tasks only. See [tasks.*] jitter. |
shell | /bin/sh | Default interpreter for run scripts. Absolute path. Per-task shell overrides. See [tasks.*] Working directory & shell. |
stop_signal | "SIGTERM" | Default signal that opens the stop ladder before SIGKILL. One of SIGTERM, SIGINT, SIGQUIT, SIGHUP, SIGKILL, SIGUSR1, SIGUSR2. Per-task / per-service stop_signal overrides. |
exit_codes | [0] | Default set of exit codes treated as success. Per-task / per-service exit_codes overrides. Codes are 0–255. |
log_max_size | 100MB | Default per-run log cap. Units b/kb/mb/gb/tb. Bare 0 and negatives are rejected. |
log_on_full | "drop_old" | Default overflow policy: drop_new, drop_old, kill_task. |
keep_runs | (unset) | Default row-count retention. Positive integer; hard internal cap of 1 000 000. Zero means “inherit from task-level setting” — only negatives are rejected. |
keep_for | (unset) | Default age-based retention. Accepts days (d) and weeks (w) on top of the usual h/m/s/ms. Zero / negative durations are rejected, as are absurdly large values (over ~100 years) — a typo guard. |
healthy_after | "60s" | Uptime a service instance must reach to count as healthy — resets its restart counter and clears the failed-start streak; faster failures count toward start_retries. Services only. Per-service override. |
start_retries | 3 | Consecutive failed starts a service instance tolerates before going FATAL and stopping. Services only. Per-service override. |
env | (none) | Inline KEY/VALUE map merged into every task and service. Task-level entries override on key collision. See [tasks.*] Environment & secrets. |
env_file | (none) | Path to a dotenv file merged beneath [defaults.env]. Values are visible in the API/UI, like the rest of env. |
secrets | (none) | Inline KEY/VALUE map merged into every task and service, but never shown in the API/UI. Task-level secrets override on key collision. |
secrets_file | (none) | Path to a dotenv file merged beneath [defaults.secrets]. Only the path is visible; keys and values never leave the daemon. |
notify_on_missed | true | Whether missed scheduled runs alert. Set false here to silence the alert daemon-wide; a per-task notify_on_missed still wins. |
Some things you can’t default: cron, on_overlap, the retry_*
keys, max_concurrent, queue_max, graceful_stop, and
params. Those are too tied
to a task’s intent — defaulting them would quietly change behaviour you
meant to be explicit. Per-task graceful_stop just falls back to its
built-in value when you leave it off, so set it only on the tables that
need a different window.
jitter is the deliberate exception. It’s a pure load knob — it caps how
far a run’s start may slip, never what it does or whether it fires — so
a defaulted jitter can’t surprise you the way a defaulted cron or
retry_* would. And pacing the whole schedule at once is exactly the
job you want a single line for: drop jitter = "30m" in [defaults] and
every cron task joins the same one-at-a-time gate, no per-task repetition.
The daemon-wide [daemon] shutdown_timeout is its own separate
top-level setting that bounds the whole-daemon shutdown phase. Each
task and service still gets its own graceful_stop window inside that
cap, and if a graceful_stop runs longer than [daemon] shutdown_timeout, the daemon warns you about that task at boot.
Precedence
Section titled “Precedence”For each task or service:
- If the field is set on the
[tasks.*]/[services.*]table, use that. - Else if it’s set in
[defaults], use that. - Else fall back to the built-in default (e.g.
100MBforlog_max_size).
Leave a numeric retention field off at every level and you get no
cap — runs just pile up until you set a value somewhere. Zero means
“inherit from the layer above” for keep_runs (only negatives are
rejected); keep_for rejects both zero and negatives. Out-of-range
values are rejected at config load, so “I forgot to set a cap” can never
quietly turn into “everything got deleted.”
Worked example
Section titled “Worked example”[defaults]timeout = "30m" # most tasks should die after 30 minuteslog_max_size = "50mb" # smaller default, override for noisy taskskeep_runs = 100keep_for = "30d"healthy_after = "30s" # services that stabilise in 30s count as healthy
[tasks.heartbeat]cron = "*/5 * * * *"run = "/usr/local/bin/heartbeat"# inherits 30m timeout, 50mb log cap, 100 runs, 30d retention
[tasks.nightly-export]cron = "0 2 * * *"timeout = "4h" # overrides default — exports take longerlog_max_size = "500mb" # overrides default — output is largegraceful_stop = "20s" # this one needs more than the built-in 5srun = "/usr/local/bin/export"
[services.flaky-worker]healthy_after = "2m" # overrides default — this one takes longer to stabiliserun = "/usr/local/bin/worker"What [defaults] doesn’t do
Section titled “What [defaults] doesn’t do”- It doesn’t apply to
[storage]— that’s a global cap, not a per-task default. - It doesn’t apply to
[notify]settings (coalesce_window,history_keep, etc.). - It doesn’t apply to
[daemon] shutdown_timeout— that’s a daemon-wide setting, not a per-task value to inherit. - Notification routing (
notify_on_failure,notify_on_success) has no defaulting layer — you set those channel lists per task. Thenotify_on_missedtoggle is the exception: it’s a simple on/off, so it does inherit from[defaults](see the table above).