Added
- Update check — the daemon checks for a newer release and shows an amber indicator next to the version in the Web UI and TUI. Never auto-updates; opt out with
[daemon] check_updates = false. - Per-service stop and restart from the CLI —
runwisp stop <service>stops one service andrunwisp restart <service>restarts it (starting it if it was stopped), leaving the rest of the daemon running. [services.*]can setmanual_trigger = falseto lock a service against manual stop, restart, and start, from the CLI, REST API, Web UI, and TUI, until you editrunwisp.tomland reload.runwisp import systemdconverts systemd.serviceunits intorunwisp.toml— a unit withRestart=becomes a service, aType=oneshotunit a task — flagging anything it can’t model (multipleExecStart,Type=notify, sandboxing, socket activation) with inline# TODOs. See From systemd.[daemon] trusted_proxiessets the reverse-proxy CIDR allowlist in TOML; theRUNWISP_TRUSTED_PROXIESenv var still works and overrides it. Catch-all ranges (0.0.0.0/0,::/0) are rejected at config load.runwisp service installnow sets a stable Web UI password. With auth on, it persists a password into a0600drop-in beside the unit — a freshly generated one (printed once), or your ownRUNWISP_PASSWORDif you set it at install time — so a managed daemon no longer mints a new password — logging every session out — on each restart. Re-installing never rotates it.runwisp service installnow carries yourRUNWISP_*environment into the service —RUNWISP_AUTH,RUNWISP_TLS,RUNWISP_CLOUD_TOKEN, and the rest, into a0600drop-in beside the unit, refreshed on every re-install. A systemd unit or launchd plist never inherited these before, so they were silently dropped.[defaults]now acceptsrestart_delay,restart_backoff,catch_up, andgraceful_stop, so you can set these fleet-wide once instead of repeating them on every unit.- Compose per-service overrides (
[compose.<alias>.<svc>]) can now setfailures, matching what[services.*]already allows.
Changed
- The
agent-guidesubcommand is replaced by a machine-readable docs pointer. Runningrunwisp --helpin a non-interactive shell now appends a link tohttps://docs.runwisp.com/llms.txtfor AI coding agents. runwisp reloadnow warns when it leaves a service stopped whose definition hasautostart = true, pointing you atrunwisp restart <service>— a reload reconciles definitions but never starts a stopped service.- The dashboard drops the runner-facts strip. The instance fingerprint now sits in the sidebar footer beside the version, and uptime is now one of the overview stat panes.
- The dashboard’s “running now” stat pane is now “total runs”, showing every run ever recorded with compact large numbers (e.g.
55.6k,1.4m). The overview “Up next” list shows the next five upcoming tasks with a+N morefooter for the rest. treat_missed_as_failureandexit_codesare replaced by onefailureskey on[defaults],[tasks.*], and[services.*]. List the outcomes that count as a failure — end-reason names (failed,timeout,crashed,missed,stopped, …) and exit codes or inclusive ranges ("42","1-23") — to drive the failed stat, UI badges, the runs-browser Failed filter, and per-tasknotify. A bare list replaces the inherited set; prefix every token with+/-(["-missed"],["+stopped"]) to add or drop a single outcome without restating the default. Exit0is always success and any non-zero exit isfailed; there’s no success-code allowlist anymore. See What counts as a failure.[compose.*]includeandexcludeare replaced by oneserviceskey. Bare or+nameentries keep only those services;-nameentries drop them; the two polarities are mutually exclusive.notify_on_failureandnotify_on_successare replaced by onenotifykey on[tasks.*],[services.*], and compose overrides. It pages when the unit’sfailurespolicy classifies a run as failed; route any non-failure outcome (a success ping, a timeout-only escalation) with an explicit[[route]]. See Per-task notifications.restartandrestart_attemptsare no longer accepted on[tasks.*]— both are now rejected at load. A task re-runs a failed run withretry_attempts/retry_delay/retry_backoff, which are fully tunable;restartstays on[services.*]for instance supervision.[[route]]match.kindsnow speaks the same outcome vocabulary asfailures(failed,timeout,crashed,log_overflow,stopped,missed, … plusservice.fatal,log.disk_pressure) instead of therun.*stream names, added a first-classmatch.failure = truethat matches any run classified as a failure, and dropped the derivedmatch.severityaxis. See Notification rules.- Run history now distinguishes
ui(Web UI / TUI “Run Now”) andcli(runwisp run) from a rawapiREST call, instead of tagging all three the same way. on_overlapis now rejected on[services.*](and compose per-service overrides): it was a silent no-op there, since a service’s concurrency isinstances, not overlap.[scheduler] timezonemoved to[daemon] timezone. The single-key[scheduler]table is gone; a config that still has one fails to load with a hint pointing at[daemon].- Every task/service-only key now rejects with a pointer to the right section (e.g.
restartunder[tasks.*],cronunder[services.*]), not just the handful that already did. [services.*]can now setrestart(never/on_failure/always, defaultalways), matching what a compose-imported service’s per-service override already allowed.- Every duration field (
timeout,retry_delay,graceful_stop,jitter, etc.) now acceptsd(days) andw(weeks), not justkeep_for— matching what the schema already advertised. GET /api/daemon’staskslist now returns the same full task shape asGET /api/tasks(description, timeout, retry/restart settings, and more), instead of a separately-trimmed subset.GET /api/runsgained anisFailurequery parameter, matching theisFailurefield already accepted in bulk-operation request bodies.POST /api/runs/bulk/deletenow reports the runs it skipped for being active in askippedlist, instead of silently dropping them from theaffectedcount; deleting a single active run still returns a409.- The auth endpoints (
/api/auth/status,/api/auth/challenge,/api/auth/login,/api/auth/launch-ticket) are now documented in the OpenAPI spec (runwisp openapi), so API clients get generated request/response types instead of guessing the shape. GET /api/notifications/streamhas been removed. Notification events already ride the unifiedGET /api/events/stream.GET /api/notifications/unreadCountis renamed toGET /api/notifications/unread-count, so every REST path uses kebab-case. TheunreadCountJSON field is unchanged.catch_upis now an integer andmax_catch_up_runsis gone. The value is how many missed cron ticks to re-fire on startup:0(was"skip"),1(was"latest", the default), orNto replay up toN(replacing"all"+ its separate cap). Anything above1still requireson_overlap = "queue". See Missed ticks.notify.coalesce_outboundis gone;coalesce_window = "0s"now disables outbound coalescing (one message per event). The bell still coalesces on its default window.[notify] keep_occurrencesis renamed tocoalesce_limit. It also controls how often a suppressed event is force-forwarded to outbound channels, which is now documented.graceful_stop = "0s"andretry_delay = "0s"are now honored literally instead of silently falling back to the defaults — sograceful_stop = "0s"means kill immediately andretry_delay = "0s"means retry with no delay.stop_signalnow takes only the canonicalSIGxxxspelling; bare names likeTERMare rejected. UseSIGTERM,SIGINT, etc.- Setting
[daemon] metrics_listennow enables metrics on its own — you no longer also needmetrics_enabled = true. [notifiers.*]now rejects fields that don’t belong to the notifier’stype(e.g.host/porton atype = "slack"block) instead of silently ignoring them.GET /api/tasks/{taskName}/runshas been removed. UseGET /api/runs?taskName=<name>, which returns the same result.
Fixed
- A daemon crash no longer leaves an attached TUI’s terminal filled with garbled escape sequences. A panic in a daemon goroutine is now turned into a clean shutdown that restores the terminal, and
runwisp cloudruns its daemon as a separate process so a crash can never corrupt the terminal the TUI owns. - Config validation errors for
[services.*]entries now say “service” instead of mislabeling the entry as a “task”. - The dashboard System resources chart now backfills its history on load instead of only drawing new samples as they stream in.
- An ad-hoc dispatch request can no longer trigger a
[services.*]entry, which could have reserved it an extra instance outside its restart policy — it only checkedmanual_trigger(always true internally for services), the same gate the REST/UI/CLI trigger paths already close with a service-kind check. - A remote
service:removerequest could delete a TOML-defined[services.*]entry, desyncing the running task set fromrunwisp.tomlwith no way back short of a daemon restart. It now only removes services that were remotely declared in the first place. runwisp run --standalonenow honorsmanual_trigger = falseand refuses to run a[services.*]entry, matching the guard the daemon already enforces.tls = "off"is now rejected whentls_cert/tls_keyare also set, instead of being silently overridden into HTTPS.- The failure badge and “Failed” filter could go stale for a run updated live over SSE, since the push validation was silently dropping the run’s failure classification. Fixed by validating the full run shape.
- A daemon booted with
runwisp cloudnever resolved a run leftpendingby a prior crash. Only standalone boot reconciled crash-orphaned pending runs; a cloud-mode restart now marks them interrupted like every other boot path. - A transient database error while marking crashed runs at boot is now retried a few times before giving up, instead of silently skipping crash recovery for that boot.
- Cron jitter for a task without its own
timezonewas placed using the host OS’s timezone instead of[daemon] timezone. The two now agree, so jittered tasks land on the intended point of the schedule. - Run filters and retention cutoffs compared timestamps as text instead of as time. A run created in a timezone other than the daemon’s own could sort or filter incorrectly around a
createdAfter/createdBeforeboundary, or near the host’s own DST transition. Timestamps are now normalized to UTC before being stored or compared. - Retention pruning a run now publishes
run.deleted, so an open dashboard reflects the deletion live instead of only after a refresh. GET /api/runs/{runId}/log/rawno longer buffers the whole log in memory before responding, closing off a memory-exhaustion risk on very large logs.- Daemon shutdown now closes open SSE connections (
/api/events/streamand the log streams) instead of leaving their handlers running past the shutdown deadline while other subsystems tear down. runwisp import supervisordcould write an invalidstop_signal(e.g. fromstopsignal=CONT) with no warning, instead of flagging it like every other field it can’t map.- A run rejected for a full queue (
queue_full) no longer offers a “Rerun” action in the TUI. Like a skipped or missed run, it was never actually executed. - The TUI now shows an error when a service stop or restart is rejected, instead of silently dropping it into the debug log.
- Status labels for underscore end reasons (e.g.
log_overflow) rendered as one unbroken word. They’re now humanized the same as every other status.
Security
- Secret values (
secrets/secrets_file) are now redacted from a run’s captured output before it’s persisted, streamed over the API, or pushed to the control plane, swapping the literal value for[redacted]. Best-effort: a secret the process transforms or splits across lines before printing can still slip through.
- v1.5.1 latest
- v1.5.0
- v1.4.0
- v1.3.1
- v1.3.0
- v1.2.0
- v1.1.0
- v1.0.1
- v1.0.0
- v0.16.4 pre
- v0.16.3 pre
- v0.16.2 pre
- v0.16.1 pre
- v0.16.0 pre
- v0.15.1 pre
- v0.15.0 pre
- v0.14.0 pre
- v0.13.2 pre
- v0.13.1 pre
- v0.13.0 pre
- v0.12.0 pre
- v0.11.0 pre
- v0.10.0 pre
- v0.9.0 pre
- v0.8.0 pre
- v0.7.0 pre
- v0.6.0 pre
- v0.5.0 pre
- v0.4.0 pre
- v0.3.0 pre
- v0.2.0 pre
- v0.1.2 pre
- v0.1.0 pre