How to Set Up Scheduled Recipe Execution in Goose Using Cron Expressions

Goose includes a built‑in, in‑process scheduler that runs recipes automatically using standard cron expressions or shorthand macros like @hourly, storing job definitions in a local JSON file and executing them via tokio_cron_scheduler.

The block/goose repository ships a native scheduling subsystem that turns any recipe into a time‑driven automation without external cron services. This guide explains how to configure scheduled recipe execution in Goose using the CLI and the internal scheduler API.

Understanding the Built-in Scheduler Architecture

The scheduler is an in‑process component that persists job definitions to disk and triggers recipe execution when cron expressions fire.

Scheduler Initialization and Storage

When a CLI command first interacts with the scheduler, Goose initializes a Scheduler instance that points to two specific locations inside the Goose data folder: schedule.json (the persistence file) and scheduled_recipes (the internal storage directory). The helper functions get_default_scheduler_storage_path() and get_default_scheduled_recipes_dir() in [crates/goose/src/scheduler.rs](https://github.com/block/goose/blob/main/crates/goose/src/scheduler.rs#L32-L45) resolve these paths automatically.

The ScheduledJob Data Structure

Each scheduled job is represented by the ScheduledJob struct defined in [crates/goose/src/scheduler.rs](https://github.com/block/goose/blob/main/crates/goose/src/scheduler.rs#L84-L99). This structure stores:

  • A unique job ID
  • The source path of the original recipe file
  • The cron expression or shorthand
  • Runtime state including last_run and paused flags

Adding and Managing Scheduled Jobs

The CLI provides a complete workflow for defining, validating, and controlling scheduled recipes.

Validating Cron Expressions

Before adding a job, Goose validates the cron string using validate_cron_expression() in [crates/goose-cli/src/commands/schedule.rs](https://github.com/block/goose/blob/main/crates/goose-cli/src/commands/schedule.rs#L10-L57). This function accepts standard 5‑field cron syntax, 6‑field syntax with seconds, or single‑word shorthands such as @hourly and @daily. It checks for empty strings, correct field counts, and prints helpful syntax suggestions on error.

Creating New Scheduled Recipes

The handle_schedule_add() function (lines 68‑103 of [crates/goose-cli/src/commands/schedule.rs](https://github.com/block/goose/blob/main/crates/goose-cli/src/commands/schedule.rs#L68-L103)) orchestrates job creation:

  1. It builds a ScheduledJob from CLI arguments.
  2. It instantiates Scheduler::new(storage_path, session_manager).
  3. It calls scheduler.add_scheduled_job(job, true).await.

During this process, the scheduler copies the recipe from the user‑provided path into the internal scheduled_recipes directory, renaming it to <schedule-id>.<ext> (logic around final_recipe_path at lines 1040‑1112 of the scheduler). The system surfaces specific SchedulerError variants—such as JobIdExists or RecipeLoadError—if conflicts arise.


# Add an hourly job using standard 5-field cron

goose schedule add \
    --schedule-id hourly-report \
    --cron "0 * * * *" \
    --recipe-source ./recipes/report.yaml

# Add a daily job using the @daily shorthand

goose schedule add \
    --schedule-id daily-backup \
    --cron "@daily" \
    --recipe-source ./recipes/backup.yaml

Listing, Running, and Removing Jobs

The CLI exposes full lifecycle management through [crates/goose-cli/src/commands/schedule.rs](https://github.com/block/goose/blob/main/crates/goose-cli/src/commands/schedule.rs):

  • handle_schedule_list() (lines 39‑72) displays all jobs with their status, cron expression, and internal recipe path.
  • handle_schedule_run_now() (lines 40‑62) manually triggers a job outside its scheduled time.
  • handle_schedule_remove() (lines 75‑92) deletes the job definition and removes its stored recipe file from scheduled_recipes.

# List all scheduled jobs

goose schedule list

# Manually trigger a job immediately

goose schedule run-now --schedule-id hourly-report

# Remove a schedule and its stored recipe

goose schedule remove --schedule-id daily-backup

Cron Expression Syntax and Shorthands

Goose accepts standard cron syntax plus common shorthands. To view a concise cheat‑sheet, run:

goose schedule cron-help

The handle_schedule_cron_help() function (lines 81‑133 of the CLI schedule module) prints reference documentation for 5‑field and 6‑field expressions as well as macros like @hourly, @daily, and @weekly.

Behind the Execution Engine

When the scheduler is active, its internal loop runs on tokio_cron_scheduler (imported in [crates/goose/src/scheduler.rs](https://github.com/block/goose/blob/main/crates/goose/src/scheduler.rs#L12-L28)). At each cron trigger, the scheduler invokes run_now(&job_id), which creates a new Session via the SessionManager (provided by [crates/goose/src/session/session_manager.rs](https://github.com/block/goose/blob/main/crates/goose/src/session/session_manager.rs)) and executes the stored recipe.

Persistence is automatic: every add, remove, or pause operation serializes the job list to schedule.json. On startup, the scheduler reconstructs its in‑memory state from this file, ensuring that scheduled recipe execution in Goose survives process restarts without data loss.

Summary

  • Goose embeds a native scheduler that requires no external cron service.
  • Jobs are defined by the ScheduledJob struct and stored in schedule.json with recipes copied to scheduled_recipes.
  • The CLI validates cron expressions via validate_cron_expression() and supports standard 5‑field, 6‑field, and shorthand (@hourly, @daily) syntax.
  • Execution is powered by tokio_cron_scheduler, which triggers new Sessions through the SessionManager.
  • Use goose schedule add, list, run-now, and remove to manage automation workflows.

Frequently Asked Questions

What cron syntax does Goose support?

Goose supports standard 5‑field cron (minute, hour, day, month, weekday), 6‑field cron with seconds, and single‑word shorthands such as @hourly, @daily, and @weekly. The validate_cron_expression() function in the CLI enforces these formats and provides actionable error messages when validation fails.

Where are scheduled recipes stored on disk?

The scheduler stores job metadata in schedule.json and copies recipe files into the scheduled_recipes directory inside the Goose data folder. The helper functions get_default_scheduler_storage_path() and get_default_scheduled_recipes_dir() in crates/goose/src/scheduler.rs define these locations.

Can I run a scheduled job manually before its next cron trigger?

Yes. The goose schedule run-now --schedule-id <id> command invokes handle_schedule_run_now(), which immediately executes the recipe through the SessionManager without waiting for the next cron tick or modifying the schedule.

What happens to schedules when Goose restarts?

Jobs persist automatically. The scheduler serializes the job list to schedule.json after every modification and reads this file on startup to reconstruct the schedule, ensuring that cron triggers resume exactly where they left off.

Have a question about this repo?

These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →