Skip to content

Bug: Scheduler panics with unwrap() if recipe YAML has neither prompt nor instructions #7431

@marlonbarreto-git

Description

@marlonbarreto-git

Bug Description

The scheduler's execute_job function panics at runtime when a recipe YAML file contains neither a prompt nor instructions field. The CLI path does not validate recipe content when scheduling — only the cron expression is validated.

Affected Code

File: crates/goose/src/scheduler.rs, lines 781-785

let prompt_text = recipe
    .prompt
    .as_ref()
    .or(recipe.instructions.as_ref())
    .unwrap();   // panics if both are None

Recipe loading (lines 724-735) has no validation:

let recipe: Recipe = {
    let extension = recipe_path.extension()...;
    match extension.as_str() {
        "json" | "jsonl" => serde_json::from_str(&recipe_content)?,
        _ => serde_yaml::from_str(&recipe_content)?,
    }
};
// No call to validate_recipe_template_from_content()

CLI path (crates/goose-cli/src/commands/schedule.rs) only validates cron:

validate_cron_expression(&cron)?;   // only validates cron
// recipe content is NOT parsed or validated

Impact

  • A user creates a minimal recipe YAML (e.g., just extensions: and activities: without prompt or instructions)
  • goose schedule add accepts it without error
  • When the cron job fires, the scheduler thread panics
  • The panic is silent — no user-visible error at schedule time, only a crash at execution time
  • The validate_prompt_or_instructions function exists in crates/goose/src/recipe/validate_recipe.rs but is never called by the scheduler

Steps to Reproduce

  1. Create a recipe file test.yaml:
    extensions:
      - developer
    activities:
      - type: ask
  2. Schedule it: goose schedule add --id test --cron "0 * * * *" --recipe test.yaml
  3. Wait for the cron to fire (or manually trigger)
  4. Observe: thread panic at scheduler.rs:785

Proposed Solution

Add recipe content validation in execute_job after deserialization (line 735), before accessing prompt/instructions:

let recipe: Recipe = serde_yaml::from_str(&recipe_content)?;

// Add validation
if recipe.prompt.is_none() && recipe.instructions.is_none() {
    return Err(anyhow::anyhow!(
        "Recipe must specify at least one of `instructions` or `prompt`"
    ));
}

Or, call the existing validate_recipe_template_from_content function that already performs this check.

Happy to submit a PR for this fix.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions