Hmbown/CodeWhale · error

fleet task {task_id} has duplicate tag {tag}

Error message

fleet task {task_id} has duplicate tag {tag}

What it means

A fleet task spec was rejected during load because its `tags` array contains the same exact string twice. `validate_tags` (crates/tui/src/fleet/task_spec.rs) walks the tag list and inserts each tag into a BTreeSet; a second identical insert fails. Comparison is exact string equality (only the emptiness check trims), so "api" and " api " are distinct, but "api" twice is a duplicate.

Source

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

pub fn record_verification_receipt(
    ledger: &FleetLedger,
    workspace: &Path,
    input: &FleetTaskVerificationInput,
    verification: FleetTaskVerification,
) -> Result<FleetReceipt> {
    let receipt = prepare_verification_receipt(workspace, input, verification)?;
    ledger.record_receipt(receipt.clone())?;
    Ok(receipt)
}

fn validate_tags(task_id: &str, tags: &[String]) -> Result<()> {
    let mut seen = BTreeSet::new();
    for tag in tags {
        if tag.trim().is_empty() {
            bail!("fleet task {task_id} tag cannot be empty");
        }
        if !seen.insert(tag) {
            bail!("fleet task {task_id} has duplicate tag {tag}");
        }
    }
    Ok(())
}

fn validate_workspace_requirements(task: &FleetTaskSpec) -> Result<()> {
    let Some(workspace) = &task.workspace else {
        return Ok(());
    };
    let env = workspace.environment.as_ref();
    for name in env
        .into_iter()
        .flat_map(|env| env.required.iter().chain(env.allowlist.iter()))
    {
        if name.trim().is_empty() {
            bail!(
                "fleet task {} environment variable name cannot be empty",
                task.id

View on GitHub (pinned to 0c42157ee5)

Solutions

  1. Find the task named in the message and delete the repeated tag so each appears once.
  2. If the spec is generated, deduplicate with a set before serializing: tags.sort(); tags.dedup();
  3. If you intended two similar tags, note the check is exact-match: differing whitespace/case counts as distinct, so normalize your tag source (trim, lowercase) at generation time instead.

Example fix

# before (fleet task TOML)
[[tasks]]
id = "audit-1"
tags = ["backend", "audit", "backend"]

# after
[[tasks]]
id = "audit-1"
tags = ["backend", "audit"]
Defensive patterns

Strategy: validation

Validate before calling

fn check_tags(tags: &[String]) -> Result<(), String> {
    let mut seen = std::collections::BTreeSet::new();
    for tag in tags {
        let t = tag.trim();
        if t.is_empty() {
            return Err(format!("empty tag: {tag:?}"));
        }
        if !seen.insert(t) {
            return Err(format!("duplicate tag: {tag:?}"));
        }
    }
    Ok(())
}

Type guard

fn has_duplicate_tags(tags: &[String]) -> bool {
    let mut seen = std::collections::BTreeSet::new();
    tags.iter().any(|t| !seen.insert(t.trim()))
}

Prevention

When it happens

Trigger: A TOML or JSON fleet task document (loaded via `load_task_spec_document`) with e.g. `tags = ["backend", "backend"]` on one task. Also specs generated programmatically that concatenate default tags with user tags without deduplicating.

Common situations: Copy-pasting tag lists between tasks; YAML/TOML anchors or merges that repeat a tag; generators that append a default tag ("fleet", "audit") on top of one the author already wrote.

Related errors


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