Scheduled tasks
KLYRN records every run of a scheduled task and shows you what it printed.
Adding one
klyrn cron example.com # what is scheduled, and how each one last went
klyrn cron runs 12 # every recorded run of one entry
klyrn cron run 12 # run it now, as the account
The schedule is five cron fields or one of @hourly, @daily,
@weekly, @monthly, @yearly, @reboot.
Anything else is refused with the example.
Entries run as the site's own Unix account. The list also shows the account's other entries: imported ones, and the WordPress cron KLYRN installs in place of WP-Cron.
What it did
Every run is recorded: when it started, how long it took and what it exited with. The first 8 KB it printed is kept, from both stdout and stderr. The last 50 runs of each entry are kept.
Worked, 1 ms
hello from cron / and stderr / 2026-09-06
Three outcomes are named: Worked for exit 0, Exit N for anything else, and Did not start for exit 127. Exit 127 means a missing interpreter or a command that is not there.
Output is still passed through as cron expects, so an operator who has set a
MAILTO keeps receiving it.
Running one on demand
Any entry can be run immediately, from the panel or the command line. It runs as the account, through the account's shell, as cron would. The run is recorded and marked as one you started.
Running an entry is a mutating operation, so a read-only token cannot start one. The
panel refuses it for a token without the sites scope.
How it works, and why it is safe
The command is not in the crontab. Each entry is a script under
/home/<account>/.klyrn-cron/<id>.sh, owned by root and readable
by the account. The crontab line runs a wrapper that names only the id:
*/5 * * * * sklyrn /opt/klyrn/bin/klyrn cron-exec 1
An unescaped % means
newline and is fed to the command's stdin, and a stray quote breaks the file. Cron then
refuses the whole file. With the wrapper, every line has the same shape.
The wrapper runs as the account with no privilege: it cannot read the database or reach the core daemon. It works out which script to run from its own identity, so asking for another account's id finds nothing. It runs the job first, then drops a record in a spool directory that account users can write but not list (mode 1733). Core sweeps that spool. It checks that the file's owner is the account that owns the job before it accepts the record.
Both halves were tested on a live server. cron-exec 1 as a second tenant
answered job 1 is not one of tenant2's. A record forged by that tenant for the same job was refused. The log says:
uid 1005 tried to write history for account sklyrn.
Recording can never break a job. The job runs first, every failure in the recording path is swallowed, and the wrapper exits with the job's own exit code.