{"record":{"id":"2388c79bb085ff95","repo":"Hmbown/CodeWhale","slug":"duplicate-fleet-worker-id","errorCode":null,"errorMessage":"duplicate fleet worker id {}","messagePattern":"duplicate fleet worker id (.+?)","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"crates/tui/src/fleet/task_spec.rs","lineNumber":141,"sourceCode":"        }\n        validate_fleet_name(&format!(\"task {} name\", task.id), &task.name)?;\n        if task.instructions.trim().is_empty() {\n            bail!(\"fleet task {} instructions cannot be empty\", task.id);\n        }\n        if let Some(objective) = &task.objective\n            && objective.trim().is_empty()\n        {\n            bail!(\"fleet task {} objective cannot be empty\", task.id);\n        }\n        validate_worker_profile(&task.id, task.worker.as_ref())?;\n        validate_tags(&task.id, &task.tags)?;\n        validate_workspace_requirements(task)?;\n    }\n    let mut worker_ids = BTreeSet::new();\n    for worker in &doc.workers {\n        validate_fleet_identity(\"worker id\", &worker.id)?;\n        if !worker_ids.insert(worker.id.clone()) {\n            bail!(\"duplicate fleet worker id {}\", worker.id);\n        }\n        validate_fleet_name(&format!(\"worker {} name\", worker.id), &worker.name)?;\n    }\n    Ok(())\n}\n\nfn validate_fleet_identity(field: &str, value: &str) -> Result<()> {\n    if value.is_empty() {\n        bail!(\"fleet {field} cannot be empty\");\n    }\n    if value.len() > MAX_FLEET_ID_BYTES || !value.chars().all(is_worker_token_char) {\n        bail!(\n            \"fleet {field} must be a simple ASCII token no longer than {MAX_FLEET_ID_BYTES} bytes\"\n        );\n    }\n    Ok(())\n}\n","sourceCodeStart":123,"sourceCodeEnd":159,"githubUrl":"https://github.com/Hmbown/CodeWhale/blob/0c42157ee52f9d55af2b506d71b46249910f77d3/crates/tui/src/fleet/task_spec.rs#L123-L159","documentation":"validate_task_spec_document iterates the top-level workers array (also accepted under the legacy alias worker_specs) and bails when a worker id is inserted into a BTreeSet twice. Worker ids address lease assignment and receipts, so duplicates are rejected before the run starts.","triggerScenarios":"Two entries in the workers array sharing the same \"id\" field, regardless of their other fields differing.","commonSituations":"Scaling a spec by copy-pasting a worker block and forgetting to change the id; merging specs from teams that both used \"worker-1\"; generators emitting a constant id per worker.","solutions":["Find the duplicated worker id from the error and rename one entry","Give generated workers sequential or role-based ids (builder-1, builder-2)","When merging spec files, namespace worker ids by their source"],"exampleFix":"// before\n\"workers\": [ { \"id\": \"w1\", \"name\": \"a\" }, { \"id\": \"w1\", \"name\": \"b\" } ]\n\n// after\n\"workers\": [ { \"id\": \"w1\", \"name\": \"a\" }, { \"id\": \"w2\", \"name\": \"b\" } ]","handlingStrategy":"validation","validationCode":"fn worker_ids_unique(workers: &[FleetWorkerSpec]) -> bool {\n    let mut seen = std::collections::BTreeSet::new();\n    workers.iter().all(|w| seen.insert(w.id.as_str()))\n}","typeGuard":"function hasUniqueWorkerIds(ws: { id: string }[]): boolean {\n  return new Set(ws.map((w) => w.id)).size === ws.length;\n}","tryCatchPattern":"if let Err(err) = load_task_spec_document(&path) {\n    if let Some(id) = err.to_string().strip_prefix(\"duplicate fleet worker id \") {\n        eprintln!(\"worker id '{id}' appears twice in {path:?}\");\n    }\n    return Err(err);\n}","preventionTips":["Generate worker ids from a counter when scaling worker blocks programmatically","Remember the legacy worker_specs key maps to the same workers array - check both after migrations","Namespace worker ids by source when merging specs"],"tags":["fleet","task-spec","workers","duplicate-id","validation"],"backgroundTag":"duplicate-identifier","analyzedSha":"0c42157ee52f9d55af2b506d71b46249910f77d3","analyzedAt":"2026-08-20T21:50:45.477Z","schemaVersion":2},"datasetVersion":"2026-08-21T13:17:26.733Z"}