← all releases

v0.16.0

pre-release on GitHub

Not the newest. v1.5.1 is out →

Release notes

Added

  • GET /api/tasks/{taskName} returns a single task without paging through the full task list.

Changed

  • The CLI command that runs a task once is now runwisp run <task> (was runwisp exec), matching the run = config key and freeing exec from colliding with compose_mode = "exec".
  • Disable authentication with RUNWISP_AUTH=off (was RUNWISP_NO_AUTH=1), matching the tls = "off" convention. RUNWISP_AUTH and RUNWISP_PASSWORD remain mutually exclusive.
  • [notify] retention keys are now keep_notifications and keep_for (were history_keep and history_keep_for), matching the keep_runs / keep_for vocabulary tasks already use.
  • [notify] knobs retry_budget and keep_occurrences (were default_timeout and occurrence_ring) name the outbound retry cap and the coalesce-occurrence count by what they do rather than by their implementation.
  • A compose block’s import strategy is now import = "services" | "stack" (was mode), so it reads distinctly from a task’s compose_mode.
  • Run timestamps on the API are now startedAt / endedAt (were startAt / endAt), including the ?sortField=startedAt value — matching the database columns and the neighbouring createdAt.
  • The task parameter field is allowCustom on the REST API (was allow_custom), matching the API’s camelCase convention. The TOML key stays allow_custom.
  • nextRunAt on the tasks API is an RFC3339 timestamp (was a pre-formatted string), like every other timestamp field.
  • Metrics samples use timestamp / cpuUsage / memUsage (were ts / cpu / mem), matching the system-stats fields.
  • The unread-count stream event is notification.unreadCountChanged (was notifications.unread_count_changed), matching the other notification.* events.
  • List endpoints share one { items, … } envelope — GET /api/tasks, /api/runs, /api/system/metrics, and log search (was runs / hits / bare arrays).
  • Per-run REST endpoints are addressed by run ID alone — /api/runs/{runId} and its /stop, /log, /log/raw, /log/stream, and /log/line/{lineNumber}/history sub-paths (were nested under /api/tasks/{taskName}/runs/{runId}); /api/tasks/{taskName}/runs still lists a task’s runs. The daemon log stream moves to /api/daemon/log/stream.
  • Hard-kill is spelled kill in both policies — on_overlap = "kill" (was terminate) and log_on_full = "kill" (was kill_task).
  • Auth endpoints use one noun per concept — login is POST /api/auth/login (was POST /api/auth), and a launch ticket is both minted (POST) and redeemed (GET) at /api/auth/launch-ticket (redeem was GET /api/auth/launch).
  • Daemon-identity endpoints are grouped under /api/daemon — the daemon overview is GET /api/daemon (was /api/info) and the local identity probe is GET /api/daemon/identity (was /api/instance); host resource stats stay at /api/system.
  • A run’s control-plane execution ID is executionId (was externalExecutionId) on the REST API, matching the control-plane protocol.
  • A run’s end reason is succeeded (was success), matching the past-tense form of every other end reason.
  • The concurrency queue cap is max_queued (was queue_max).
  • The manual-trigger gate is manual_trigger (was api_trigger), since it also gates the CLI and web UI, not just the REST API.
  • A notifier’s TLS setting is tls_mode (was tls), naming the handshake mode it picks rather than just “on/off”.
  • Notification routes are [[route]] blocks with a notifiers list (were [[notification_route]] with notify), and notifiers are declared as [notifiers.<id>] tables keyed by id (was [[notifier]] blocks with an id field).
  • Config reload is POST /api/daemon/reload (was /api/reload), grouped with the other daemon-lifecycle endpoints.
  • The app event stream is GET /api/events/stream (was /api/stream).
  • The system metrics endpoint is GET /api/system/metrics (was /api/system/history), matching its MetricsSample payload.
  • The unread-notification-count endpoint is /api/notifications/unreadCount (was /unread-count), matching the API’s camelCase convention.
  • runOnStart is always present on the tasks API (previously omitted when false), matching manualTrigger and autostart.
  • A run that never started (skipped, missed, or interrupted by a DST fold) reports as skipped, not failed, on the control-plane connection.

Fixed

  • env_file and secrets_file values are read literally, so passwords and tokens containing $, ${...}, or # are passed to the task exactly as written.
  • A log stream resumed after a disconnect replays the most recent missed lines, keeping a reconnecting browser continuous with the live tail even across a large gap.
  • Outbound notification channels (Slack, Telegram) send periodic check-ins during a sustained incident on the default keep_occurrences cadence.
  • storage.max_size enforcement accounts for rotated log segments when trimming run history to the configured cap.
  • Starting the daemon no longer reprints the startup log a second time after a successful start. The log was already streamed live during startup; only a failed or timed-out start now prints the tail for diagnosis.
  • Running runwisp non-interactively with no runwisp.toml in the current directory now fails immediately with a clear error.
  • The config JSON Schema for stop_signal now accepts the short forms (TERM, INT, KILL, …) the daemon already did, so editor validation stops flagging configs that were always valid.
  • The unread notification badge now updates across other open tabs/sessions when a mutation (e.g. mark-all-read) changes the count without shipping a notification.
  • POST /api/tasks/{taskName}/run?wait=true’s waitTimeout now maxes out at 240s (default 120s) so it can no longer exceed the server’s write timeout and drop the response after the run had already finished.
  • Sorting runs by duration (sortField=duration) now returns them in the correct order instead of leaving them unsorted.
  • Two runwisp daemon processes started against the same data dir can no longer both proceed — ownership of the data dir is now claimed atomically.
  • A cron day-of-week field using a bare 7 with a step (e.g. 7/2) now always means Sunday alone, instead of the step wrapping into other days.
  • Daemon shutdown no longer hangs on an unresponsive Docker/Podman engine during container or compose task cleanup.
  • A run’s log output from just before a rotation is no longer deleted the instant the run ends — it stays available until the run itself is purged by retention.
  • Viewing log lines from before a run’s most recent log rotation now returns their actual content instead of coming back empty.
  • The unread notification count no longer drifts out of sync — it’s taken directly from the server on every event instead of computed from local deltas.
  • A failed log fetch now shows as an error in the log viewer instead of silently rendering as if the run produced no output.
  • Logging out mid-session now disconnects the live event stream immediately instead of letting it keep applying pushed updates behind the login screen.
  • Reloading a task away from on_overlap = "queue" no longer leaves its already-queued runs stuck pending forever.
  • The daemon’s identity fingerprint no longer changes when the binary is moved or the hostname changes — only the machine ID and working directory are hashed.
  • The daemon’s live log/event streams no longer have a narrow gap where a line or notification published right as a client (re)connects could be delivered to nobody.
  • Reconnecting to the app after a dropped connection now resyncs notifications from the server instead of silently missing anything that happened during the gap.
  • Marking all notifications read no longer discounts a notification that was created while the request was still in flight.
  • The runs list sorted oldest-first no longer undercounts newly arrived runs against the list’s total.
  • A cron task reclaimed by the system’s own crond while queued in the daemon’s start-spread window could still fire once more — it’s now dropped instead of double-running for that tick.
  • A run stopped because its log hit the disk free-space guard is now recorded as a policy-initiated stop instead of looking like a crash.
  • A log rotation that fails immediately after freeing the old segment no longer risks silently destroying already-captured output on the next write.
  • A run’s output capture that stops due to a real I/O error is now logged and noted in the run’s own log instead of looking like the process simply exited.
  • A container-execution task’s own RUNWISP_-prefixed environment variables are no longer silently dropped when the task also had other env vars or secrets configured.
  • A notification delivery that timed out is no longer silently dropped without a retry or failure record — only a real daemon shutdown is now treated as a shutdown.
  • A run’s status update to the cloud control plane is no longer silently lost if the connection dropped at the exact moment it was being sent.
  • The TUI no longer freezes while confirming an action (restart/stop/trigger/retry a run) if the daemon is slow to respond.
  • Switching between runs’ logs in the TUI no longer occasionally drops or misattributes lines from the run you just left.
  • Jumping to a log search result in the TUI no longer highlights the wrong run’s line if you navigated away before it finished opening.
  • Browsers without the Web Locks API (but with BroadcastChannel) no longer open a duplicate SSE connection per tab instead of sharing one.
  • The run picker’s select-all checkbox no longer shows “all selected” after paging or filtering changed which rows were visible.
  • The run filter popover’s exit-code field no longer shows a stale value after the filter was cleared elsewhere (e.g. removing its chip).
  • The notification bell’s error indicator no longer lights up for unrelated unread notifications when an actual error notification had already been read.
  • Modal and Drawer dialogs now trap keyboard focus while open and restore it on close, instead of letting Tab escape to the page behind them.
  • Select, Dropdown, and Popover menus now keep keyboard focus inside them while open instead of letting Tab jump to an unrelated part of the page.

Security

  • Hardened service install unit generation, task-process environment isolation, config-file trust checks, privilege dropping, and outbound-request address filtering. Internal safeguards with no configuration changes.
  • HTTP-task run logs redact request/response credentials — Authorization, Cookie, API-key headers, and URL passwords are shown as [redacted] instead of being persisted to disk.
  • Control-plane dispatch is validated and bounded at the boundary — peer-supplied shell umask/interpreter are re-validated, log-search size and the ephemeral dispatch queue are capped, and running the control-plane connection over plaintext (RUNWISP_CLOUD_ALLOW_INSECURE) now warns loudly at startup.
  • allow_cloud_dispatch now also gates HTTP-type ad-hoc dispatch and cloud-created services, not just shell/container/compose, since HTTP dispatch makes a peer-directed network call too.
  • Log search runs under a request deadline. Internal safeguard with no configuration changes.
  • A shutdown race and a panic risk in the notification retry/coalescing path are fixed. Internal safeguard with no configuration changes.
  • RUNWISP_TRUSTED_PROXIES rejects an IPv4-mapped IPv6 range that would cover every IPv4 address (e.g. ::ffff:0:0/96), closing a gap that let a peer spoof X-Forwarded-For/X-Forwarded-Proto past the trusted-proxy check.
All releases