Hmbown/CodeWhale · error

fleet task {} objective cannot be empty

Error message

fleet task {} objective cannot be empty

What it means

The objective field of a fleet task is optional, but validate_task_spec_document requires that a present objective be non-blank after trimming. An empty objective is rejected because receipts and UI surfaces would otherwise render an explicitly empty goal with no way to distinguish it from an unset one.

Source

Thrown at crates/tui/src/fleet/task_spec.rs:131

pub fn validate_task_spec_document(doc: &FleetTaskSpecDocument) -> Result<()> {
    if doc.tasks.is_empty() {
        bail!("fleet task spec must include at least one task");
    }
    let mut ids = BTreeSet::new();
    for task in &doc.tasks {
        validate_fleet_identity("task id", &task.id)?;
        if !ids.insert(task.id.clone()) {
            bail!("duplicate fleet task id {}", task.id);
        }
        validate_fleet_name(&format!("task {} name", task.id), &task.name)?;
        if task.instructions.trim().is_empty() {
            bail!("fleet task {} instructions cannot be empty", task.id);
        }
        if let Some(objective) = &task.objective
            && objective.trim().is_empty()
        {
            bail!("fleet task {} objective cannot be empty", task.id);
        }
        validate_worker_profile(&task.id, task.worker.as_ref())?;
        validate_tags(&task.id, &task.tags)?;
        validate_workspace_requirements(task)?;
    }
    let mut worker_ids = BTreeSet::new();
    for worker in &doc.workers {
        validate_fleet_identity("worker id", &worker.id)?;
        if !worker_ids.insert(worker.id.clone()) {
            bail!("duplicate fleet worker id {}", worker.id);
        }
        validate_fleet_name(&format!("worker {} name", worker.id), &worker.name)?;
    }
    Ok(())
}

fn validate_fleet_identity(field: &str, value: &str) -> Result<()> {
    if value.is_empty() {

View on GitHub (pinned to 0c42157ee5)

Solutions

  1. Either delete the objective key from the task or give it real text
  2. Configure your serializer to skip None/empty optional fields (serde skip_serializing_if on the producer side)
  3. Check the task id in the error message to find the offending block quickly

Example fix

// before
{ "id": "t1", "name": "t1", "instructions": "...", "objective": "" }

// after
{ "id": "t1", "name": "t1", "instructions": "...", "objective": "Ship the fix" }
Defensive patterns

Strategy: validation

Validate before calling

fn objectives_ok(tasks: &[FleetTaskSpec]) -> bool {
    tasks.iter()
        .all(|t| t.objective.as_deref().map_or(true, |o| !o.trim().is_empty()))
}

Type guard

function objectiveValid(t: { objective?: string }): boolean {
  return t.objective === undefined || t.objective.trim().length > 0;
}

Try / catch

if let Err(err) = load_task_spec_document(&path) {
    if err.to_string().contains("objective cannot be empty") {
        eprintln!("a task in {path:?} sets objective to blank - delete the key or fill it");
    }
    return Err(err);
}

Prevention

When it happens

Trigger: A task containing "objective": "" or "objective": " ". Omitting the key entirely passes - only a present-but-blank value bails.

Common situations: Templates that always emit the objective key; editors clearing the text but leaving the field; serializers writing empty strings for unset optional strings.

Related errors


AI-assisted analysis of Hmbown/CodeWhale@0c42157ee5 (2026-08-20). Data as JSON: /api/errors/9835f11aa06a813e. Report an issue: GitHub.