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

> Learn to set up scheduled recipe execution in Goose using cron expressions. Automate your tasks with Goose's in-process scheduler and tokio_cron_scheduler for efficient job management.

- Repository: [Block Open Source/goose](https://github.com/block/goose)
- Tags: how-to-guide
- Published: 2026-04-05

---

**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`](https://github.com/block/goose/blob/main/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)](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)](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)](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)](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.

```bash

# 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)](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`.

```bash

# 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:

```bash
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)](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)](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`](https://github.com/block/goose/blob/main/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`](https://github.com/block/goose/blob/main/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`](https://github.com/block/goose/blob/main/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`](https://github.com/block/goose/blob/main/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`](https://github.com/block/goose/blob/main/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.