Scheduled tasks
Recurring task creation — a schedule fires on a cron cadence and spawns an ordinary task that flows through the normal workflow engine.
A scheduled task turns recurring work into a first-class object. Instead of a person remembering to re-file the same task every Monday — a nightly report, a weekly dependency audit, a periodic cleanup sweep — you define the work once, attach a cadence, and let CodeHerder file it for you.
What a scheduled task is
A schedule is a recurring producer of ordinary tasks. It stores a frozen copy of the inputs a normal task takes, plus a cron expression. When the cadence fires, the scheduler creates a single, ordinary task from those stored inputs — and then does nothing else.
That spawned task is indistinguishable from a hand-filed one. The existing workflow engine picks it up, staffs it, and runs it through its stages exactly as described in How work flows. The scheduler is only a producer; it adds no new execution semantics.
Each spawned task records which schedule produced it, so its origin shows up in the activity feed and in a schedule’s Recent runs list.
Creating a schedule
A schedule takes the same inputs as a task, plus the cadence:
- The task inputs — title, description, priority, task type, and required capabilities. These are frozen into the schedule and reused on every fire (see Core concepts for what each means).
- A cron expression — a standard five-field cron string (for example
0 9 * * MONfor 9 a.m. every Monday). The named shorthands@hourly,@daily,@weekly,@monthly, and@yearlyare also accepted. - A timezone — an IANA timezone name (for example
Europe/London), defaulting toUTC. Cron without a timezone is ambiguous, so the schedule always carries one; daylight-saving transitions are handled for you. - Enabled or disabled — disable a schedule to pause it without deleting it, and re-enable it to resume. A disabled schedule never fires automatically.
- An optional due-offset — make each spawned task due a fixed interval after it is created (for example “due 2 days after spawn”). An absolute due date would be in the past after the first fire, so the offset is relative.
- Optional pre-filled fields — you can seed a canned field value, such as a standing proposal, that is written onto every spawned task. Unknown fields are skipped rather than failing the spawn.
- An optional end date — set a date and time after which the schedule stops firing automatically. Once that moment passes the schedule remains in your list but will not fire again; push the end date further out (or clear it) to resume firing. Set it in the web app when you create a schedule, and add, change, or clear it any time afterwards from the web app or the CLI — see From the CLI, below.
Where it lives
In the web app
Schedules live under Tasks ▸ Schedules. From there you can:
- see every schedule with its next and last run,
- create one (the form reuses the New Task fields and adds the cron expression, timezone, enabled toggle, due-offset, task-field seeds, and end date),
- pause a schedule by disabling it (or resume by re-enabling), edit it — including adding, changing, or clearing its end date — or delete it,
- run it now, and
- open a schedule to see its Recent runs — up to the 10 most recent tasks it has spawned.
Every schedule’s controls are visible to every member, but not every member can use them on every schedule — see Who can change a schedule below.
From the CLI
The ch schedule verb group mirrors the web app. Everywhere below, <id> also accepts the schedule’s title instead of its full ID — see Using the ch CLI for how CodeHerder matches a record by name.
ch schedule create --title "<title>" --cron "<expr>" [--tz <IANA>] [--priority high|normal|low]
[--type <type>] [--cap <capability> …] [--due-in <duration>] [--disabled]
[--description --from-file <file>]
ch schedule list [--enabled | --disabled] [--limit N]
ch schedule show <id> # next/last run + recently spawned tasks
ch schedule edit <id> [--cron …] [--tz …] [--ends-at <RFC3339>] … # alias: update
ch schedule enable <id> # resume: turn automatic firing back on
ch schedule disable <id> # pause: stop automatic firing without deleting it
ch schedule delete <id>
ch schedule run-now <id>
Use ch schedule enable <id> and ch schedule disable <id> to pause or resume a schedule — these are the canonical way to flip a schedule’s enabled state from the CLI.
Setting an end date from the CLI. ch schedule edit sets or clears a schedule’s end date; ch schedule create has no end-date flag, so give a brand-new schedule an end date either in the web app’s create form or by running ch schedule edit right after you create it.
ch schedule edit <id> --ends-at 2026-12-31T23:59:00Z # stop firing after this moment (RFC3339)
ch schedule edit <id> --ends-at "" # clear the end date — resume firing indefinitely
The timestamp must be an RFC3339 timestamp (include a timezone offset or Z); an empty string clears the end date, and the CLI rejects a malformed timestamp.
As elsewhere in the CLI, a multi-line description (--description) is passed with --from-file or on stdin, never inline.
A REST API backs both surfaces (/v1/workspaces/{id}/task-schedules for create and list, /v1/task-schedules/{id} for show, edit, and delete, and /v1/task-schedules/{id}/run-now).
Who can change a schedule
Any workspace member can create a schedule and see, list, and open every schedule in the workspace — creating your own recurring automation is self-service.
Changing one that already exists is tighter: editing, pausing, resuming, deleting, or running a schedule now requires you to be the schedule’s creator, or a workspace owner or admin. Pausing and resuming count as a change here too, the same as editing or deleting — it isn’t only the destructive actions that are gated.
A member who didn’t create a given schedule, and isn’t an owner or admin, still sees the pause, edit, delete, and run-now controls. The web app doesn’t hide them. But using one on a schedule that isn’t theirs fails with a permission error, and the equivalent CLI command (edit, enable, disable, delete, run-now) is refused the same way. Agents follow the same rule: an agent can manage a schedule it created itself, but an agent can never hold the owner or admin role, so it can’t administer a schedule that belongs to someone else.
See Members, teams, and roles for what the admin and owner roles can otherwise do.
Seeding task fields
A schedule can pre-fill task fields on every task it spawns — for example, a standing proposal or a recurring checklist item. CodeHerder writes each seed onto the spawned task at creation time; the assignee or agent can then edit those values normally.
In the web app: once you pick a task type in the create or edit form, a Task field seeds section appears — one text box per field defined by that type. Enter the value you want pre-populated on every spawned task; leave a box empty to skip that field. Child-task fields are not seedable and do not appear in the list. On the edit form, clearing a seed box and saving removes that seed: tasks spawned after that point will no longer have that field pre-filled.
From the CLI: ch schedule create seeds the task’s native description via --description. Per-type field seeds (such as a standing proposal or checklist) are web-app only — use the Task field seeds section in the create or edit form.
ch schedule edit does not support field seeds. To add or update seeds on an existing schedule, open it in the web app and use the Task field seeds section.
How schedules behave
A few behaviours are worth knowing before you rely on a schedule:
- Fire-once on catch-up — no backfill. If the server is down across several intervals, the schedule fires once when it comes back and then computes its next run forward from that point. It does not replay every missed interval, so an outage never produces a storm of tasks.
- One open task at a time. A schedule will not spawn a new task while a task it previously spawned is still open (not yet finished or cancelled). If the cron fires while that prior task is still running, the fire is skipped silently — no error is raised, the schedule’s last-run time does not update, and the skipped fire does not appear in Recent runs. The schedule simply re-evaluates at its next cron slot and spawns again once the prior task has finished or been cancelled.
- Cron is minute-resolution. The finest cadence a cron expression can express is once per minute. The schedule form recommends a minimum of five minutes between fires; this is a guideline, not a server-enforced limit.
- Run-now bypasses enabled, but not the open-task guard. Running a schedule manually fires it immediately even when it is disabled — handy for testing a schedule before you turn it on. Run-now is still subject to the same one-open-task guard, but unlike a skipped automatic fire, it tells you: if a prior task from this schedule is still open, run-now refuses and says so, rather than quietly creating a duplicate. Wait for that task to finish or cancel it, then run-now again — or just let the schedule’s own next cron fire pick it up. A manual run does not move the next scheduled fire.
- Schedules outlive their creator. A schedule belongs to its workspace, not to the member who created it. If that member leaves the workspace, the schedule keeps firing on schedule — but from then on, only a workspace owner or admin can change or remove it (see Who can change a schedule, above).
- A removed task type degrades gracefully. If the task type a schedule was built around is later removed from the workspace, that fire records an error and is skipped — the schedule and all others keep running.
Related guides
- How work flows — the pipeline a spawned task runs through
- Core concepts — task inputs and the object model
