← all releases

v1.8.0

on GitHub

Not the newest. v1.9.0 is out →

Release notes

Added

  • PHP SDK, runwisp/runwisp on Packagist: define tasks in plain PHP, or run the Laravel, Symfony or CodeIgniter 4 scheduler under RunWisp with runwisp:work. #[RunWisp\Task] on a command, job or closure adds task keys such as retry_attempts, and runwisp:check lists the tasks with their next runs and validates the config before you deploy.
  • runwisp import systemd reads .timer units and turns each timer and the service it starts into one scheduled task.
  • A hook run takes tail=N and returns the last N lines of the run’s log with the run, so a CI job holding only a hook token can show why a run failed.
  • runwisp start, stop and restart take --token (or RUNWISP_HOOK_TOKEN) with --url, so a task that runs as another user can restart a service through its hook token.
  • runwisp status and runwisp list take --url and --password to ask a remote daemon, and RUNWISP_URL now applies to them too.
  • Runs record how many lines they printed. The TUI run list has an OUTPUT column and the Web UI’s task page a third bar next to CPU and RAM, both amber when a run printed far more or far less than the task usually does, such as a silent backup that printed a warning.
  • A task’s run durations are colored like CPU and RAM in the TUI run list and the Web UI’s task page: amber from 1.5× the task’s usual run, red from twice it. The TUI task details show the usual duration.
  • [daemon] heartbeat_url pings a dead man’s switch such as healthchecks.io every minute, so you hear about it when the daemon itself is down.
  • A recovered notification tells you when a task succeeds after a failed run. It goes wherever the task’s failures go, the bell included.

Changed

  • runwisp demo starts on a free port when 9477 is taken, unless you passed --port.
  • The Web UI shows times in 24-hour format everywhere, including cron schedules (“At 03:15”) and run list dates.
  • A service that exits 0 before healthy_after counts as a failed start, so a command that daemonizes goes FATAL instead of restarting forever. Its run log says a service must stay in the foreground.
  • runwisp service uninstall lists the tasks and services that stop running before it asks, and puts back the crontab lines runwisp promote commented out.
  • runwisp takeover saves a stable Web UI password the way service install does, instead of leaving one that changes on every restart.
  • runwisp run ends with a line saying how the run ended, such as Task failed reason=timeout duration=6s exit=1.
  • Tasks that start at the same second are reported in one warning line, and any notifier now answers the warning about a crontab’s MAILTO.
  • runwisp import supervisord with no file reads /etc/supervisor/supervisord.conf or /etc/supervisord.conf, and runwisp import cron with nothing on stdin says what to pipe in.
  • runwisp import cron notes jobs that redirect their output to a file, since RunWisp can’t show that output.
  • runwisp list shows each service’s live state, such as FATAL, and runwisp status lists the services that gave up restarting.
  • The update check is skipped when the CI environment variable is set, so a daemon in a test pipeline never contacts concierge.runwisp.com.
  • A compose service whose container already runs from docker compose up attaches to that container instead of starting a second one, and runs its own container from the next restart.
  • A run killed by a signal RunWisp didn’t send names the signal in its log, also when the shell reports it as exit 128+n (such as 137), and a SIGKILL points at the kernel’s out-of-memory killer.
  • runwisp takeover says to run sudo runwisp reload after crontab -e, since an edit waits for a reload.
  • runwisp import cron suggests a [services.*] table for an @reboot job that never exits, and a job that writes its output to a file keeps a comment saying so above its table, also after runwisp promote.
  • runwisp import systemd finds a unit by name, such as backup.timer, in the systemd unit directories, and with nothing on stdin it says what to pass instead of importing an empty service.
  • An unknown key like retrys suggests the key you likely meant (retry_attempts).
  • A service now tries 10 failed starts in a row before it goes FATAL, about 5 minutes with the default backoff, instead of 3. Set restart_attempts = 3 to keep the old limit.

Fixed

  • Scheduled tasks run on time after the system clock jumps (date -s, an NTP step, a VM snapshot restore or a laptop waking up), instead of waiting out the old interval.
  • runwisp restart and runwisp service install no longer count toward systemd’s crash-loop limit, so restarting a few times in a row no longer hands the jobs back to cron.
  • @runwisp/js in an Astro app under pnpm finds the runwisp binary in the built server instead of failing at startup with “could not run runwisp”.
  • @runwisp/js in a Nuxt app on the node-cluster preset stops its tasks on SIGTERM and lets running ones finish, instead of hanging for 30 seconds and killing them.
  • withRunWisp() fails the build with output: "export", where the tasks would never run.
  • The “could not run runwisp” error says when @runwisp/js was bundled into the app and that it belongs under dependencies.
  • nuxi preview keeps the data of a Nuxt app using @runwisp/js in the project’s .runwisp, not in .output, which the next build deletes.
  • @runwisp/js apps with many fast tasks no longer slow the daemon down: the app asks for next run times at most once a second instead of after every run.
  • A task that throws a value that can’t be printed (its stack or message getter throws) fails its run instead of crashing the app.
  • A task that prints in a tight loop no longer takes down every run in the app. Output is sent in batches, and past 256 MB waiting to be sent, that task’s lines are dropped with a note in its log.
  • A task file’s process (schedule(cron, "./job.js")) gets SIGKILL once graceful_stop runs out, stop signals reach the processes it started, and they all exit when the app dies.
  • A run the app skips because the previous callback ignored its stop signal is recorded as skipped, not failed.
  • Under Bun, an app crash from an unhandled promise rejection writes its stack trace into the logs of the runs in progress, as under Node.js.
  • task() and service() reject a missing callback, schedule() rejects a task file that doesn’t exist, and registering a task after close() throws instead of being silently ignored.
  • close() logs what it is waiting for when a callback keeps running.
  • The Overview no longer overflows the screen on phones when a task has a long name, and a long group name no longer spills out of its card.
  • A long task name no longer squeezes the header search box until its shortcut hint overlaps the badges beside it.
  • A timed-out run no longer shows “Exit -1” on the Overview, and its task reads “Last run timed out” instead of “Last run failed”.
  • Run lists show statuses like “Start failed” instead of “Start_failed”.
  • A filtered run list that matches nothing says so and offers to clear the filters, instead of “No runs yet”.
  • The “end of output” marker stays visible after a very long line, and a run that printed nothing says so instead of “Waiting for logs…”.
  • Dates in another year include the year, so a next run “in 1 year (Feb 29)” names which February.
  • The Notifications page shows its name in the header.
  • Notifications no longer say “exit -1” for a killed run or “after 0s” for a run under a second, and number service instances from #1.
  • runwisp service status no longer reports a healthy system service as degraded with Linger: OFF; linger only applies to --local services.
  • Importing a crontab installed with crontab - no longer turns cron’s # DO NOT EDIT THIS FILE header into the first job’s description.
  • Importing a crontab no longer turns a commented-out job above a live one into its description, and runwisp import cron flags a missing user column only when the sixth field is a real account that isn’t also a program (find, rsync, certbot).
  • The Web UI no longer logs a blocked JavaScript eval in Firefox on first load.
  • A task the kernel kills for running out of memory no longer stops the whole systemd service and every other task with it. Run sudo runwisp service install again to update an existing unit.
  • runwisp restart checks the config first and leaves the daemon running when the config is invalid, instead of stopping it for one that can’t start.
  • Live logs and Web UI updates stream through nginx as they happen, instead of arriving in one burst.
  • A non-root runwisp list or runwisp run on a box with the system service says to use sudo, instead of failing on a missing runwisp.toml.
  • Opening a dropdown menu in the Web UI no longer scrolls the page. Thanks @dahool.
  • A failure that comes back after a successful run alerts again and gets a new bell row, instead of being held in the earlier failure’s coalesce_window.
  • runwisp takeover ends with the upgrade command for the binary the service runs, not for the copy in your home directory.
  • Errors from the daemon, like a rejected runwisp reload, print the reason instead of the raw JSON response.
  • The Web UI shows a task’s next run in the timezone its schedule uses, so “next run in 7 hours (02:30)” agrees with “At 02:30”.
  • runwisp service install --local no longer calls the password file it saved root-owned.
  • runwisp without a terminal and without a runwisp.toml fails right away with a hint, instead of leaving an empty .runwisp directory behind.
All releases