{"record":{"id":"a6ee17ee3bb4903f","repo":"Hmbown/CodeWhale","slug":"fleet-task-task-id-field-cannot-be-empty","errorCode":null,"errorMessage":"fleet task {task_id} {field} cannot be empty","messagePattern":"fleet task (.+?) (.+?) cannot be empty","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"crates/tui/src/fleet/task_spec.rs","lineNumber":193,"sourceCode":"    };\n    validate_worker_token(\n        task_id,\n        \"worker.agent_profile\",\n        worker.agent_profile.as_deref(),\n    )?;\n    validate_worker_token(task_id, \"worker.loadout\", worker.loadout.as_deref())?;\n    validate_worker_token(task_id, \"worker.model_class\", worker.model_class.as_deref())?;\n    validate_worker_model(task_id, worker.model.as_deref())?;\n    Ok(())\n}\n\nfn validate_worker_token(task_id: &str, field: &str, value: Option<&str>) -> Result<()> {\n    let Some(value) = value else {\n        return Ok(());\n    };\n    let trimmed = value.trim();\n    if trimmed.is_empty() {\n        bail!(\"fleet task {task_id} {field} cannot be empty\");\n    }\n    if trimmed != value || !trimmed.chars().all(is_worker_token_char) {\n        bail!(\n            \"fleet task {task_id} {field} must be a simple token, not a path or provider/model id\"\n        );\n    }\n    Ok(())\n}\n\nfn is_worker_token_char(ch: char) -> bool {\n    ch.is_ascii_alphanumeric() || matches!(ch, '-' | '_' | '.')\n}\n\nfn validate_worker_model(task_id: &str, value: Option<&str>) -> Result<()> {\n    let Some(value) = value else {\n        return Ok(());\n    };\n    let trimmed = value.trim();","sourceCodeStart":175,"sourceCodeEnd":211,"githubUrl":"https://github.com/Hmbown/CodeWhale/blob/0c42157ee52f9d55af2b506d71b46249910f77d3/crates/tui/src/fleet/task_spec.rs#L175-L211","documentation":"validate_worker_token checks the optional worker.loadout and worker.model_class fields of a task's worker profile. If present, a value must be non-blank after trimming; a whitespace-only string is rejected instead of silently dropping the override.","triggerScenarios":"A task with \"worker\": { \"loadout\": \"\" } or \"worker\": { \"model_class\": \"   \" }. Omitting the keys passes - only present-but-blank values bail.","commonSituations":"Spec templates that always emit both worker keys; editors clearing a loadout override; generators writing empty strings for unset optionals.","solutions":["Remove the blank worker.loadout / worker.model_class key, or set a real token value","Make your spec serializer omit keys whose value is empty","Re-check the task id in the message to locate the failing worker block"],"exampleFix":"// before\n{ \"id\": \"t1\", \"instructions\": \"...\", \"worker\": { \"model_class\": \"\" } }\n\n// after\n{ \"id\": \"t1\", \"instructions\": \"...\", \"worker\": { \"model_class\": \"frontier\" } }\n// or drop the worker.model_class key entirely","handlingStrategy":"validation","validationCode":"fn worker_tokens_ok(values: &[Option<&str>]) -> bool {\n    values.iter().flatten().all(|v| !v.trim().is_empty())\n}","typeGuard":"function optionalTokenOk(v: string | undefined): boolean {\n  return v === undefined || v.trim().length > 0;\n}","tryCatchPattern":"if let Err(err) = load_task_spec_document(&path) {\n    if err.to_string().contains(\"cannot be empty\") && err.to_string().contains(\"worker.\") {\n        eprintln!(\"{path:?}: blank worker.loadout/model_class - set a token or drop the key\");\n    }\n    return Err(err);\n}","preventionTips":["Skip optional worker keys when their value is empty rather than emitting blank strings","Treat worker.loadout and worker.model_class as tokens, not paths or model routes","Validate drafted specs with the public validator before persisting them"],"tags":["fleet","task-spec","worker","loadout","validation"],"backgroundTag":"empty-required-field","analyzedSha":"0c42157ee52f9d55af2b506d71b46249910f77d3","analyzedAt":"2026-08-20T21:50:45.477Z","schemaVersion":2},"datasetVersion":"2026-08-21T18:17:14.833Z"}