{"record":{"id":"e0e53783e8af0829","repo":"Hmbown/CodeWhale","slug":"fleet-task-selects-member-whose-role-is-but-worker-role","errorCode":null,"errorMessage":"Fleet task {} selects member {:?} whose role is {:?}, but worker.role names a different posture {:?}; a task has one posture — drop worker.role or select a member with that role","messagePattern":"Fleet task (.+?) selects member (.+?) whose role is (.+?), but worker\\.role names a different posture (.+?); a task has one posture — drop worker\\.role or select a member with that role","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"crates/tui/src/fleet/worker_runtime.rs","lineNumber":228,"sourceCode":"        };\n\n        if let Some(profile) = selected {\n            validate_selected_member_model(task, profile)?;\n            let snapshot = FrozenFleetMember::from_profile(profile);\n            // A task carries exactly one posture. When an explicit member\n            // selector is present, `worker.role` may only restate that\n            // member's role (any casing or legacy alias); naming a different\n            // posture is an authoring error, not a tie to arbitrate later.\n            // Failing closed here is what keeps the launch-time resolver\n            // honest: a read-only label can never widen to a member's write\n            // authority, and a member's read-only slot can never be widened by\n            // a write-capable label (#5945).\n            if explicit_selector.is_some()\n                && let Some(label) = legacy_role_selector.as_deref()\n            {\n                let label = canonical_public_role_name(label);\n                if label != snapshot.role {\n                    bail!(\n                        \"Fleet task {} selects member {:?} whose role is {:?}, but worker.role names a different posture {:?}; a task has one posture — drop worker.role or select a member with that role\",\n                        task.id,\n                        profile.id,\n                        snapshot.role,\n                        label\n                    );\n                }\n            }\n            task.metadata.insert(\n                FROZEN_FLEET_MEMBER_METADATA_KEY.to_string(),\n                serde_json::to_value(&snapshot)?,\n            );\n            let worker = task.worker.as_mut().expect(\"worker checked above\");\n            worker.agent_profile = Some(format!(\"member:{}\", profile.id));\n            if explicit_selector.is_none() {\n                worker.role = Some(snapshot.role.clone());\n            } else if let Some(role) = worker.role.as_mut() {\n                *role = canonical_public_role_name(role.trim());","sourceCodeStart":210,"sourceCodeEnd":246,"githubUrl":"https://github.com/Hmbown/CodeWhale/blob/73e0f67d83c59909b571efdfc88c4bc28c309cb1/crates/tui/src/fleet/worker_runtime.rs#L210-L246","documentation":"A Fleet task both selects an explicit member and carries a legacy worker.role label naming a different posture. A task has exactly one posture; freeze_fleet_task_members rejects the contradiction instead of silently letting the write-capable legacy label widen the selection (issue #5945 context).","triggerScenarios":"freeze_fleet_task_members where task.worker has both a member selector and a role label whose canonical_public_role_name differs from the selected member's role in the frozen snapshot.","commonSituations":"Editing a task's member without clearing the old role label; copying a task and swapping the member but not the role; older specs where the role field carried meaning it no longer does.","solutions":["Drop the worker.role field from the task, keeping the member selection.","Change the member selection to a member whose role matches the worker.role label.","Update worker.role to the selected member's actual role."],"exampleFix":"// before\n\"worker\": {\"member\": \"agent-1\", \"role\": \"impl\"}  // agent-1 is a reviewer\n// after\n\"worker\": {\"member\": \"agent-1\"}","handlingStrategy":"validation","validationCode":"fn posture_conflict(task: &Task, profile: &AgentProfile) -> bool { task.worker.as_ref().and_then(|w| w.role.as_deref()).map(|r| canonical_public_role_name(r) != profile.role).unwrap_or(false) }","typeGuard":null,"tryCatchPattern":null,"preventionTips":["Clear worker.role when selecting a member explicitly","Copy tasks without carrying stale role labels","Treat member selection as the single source of posture"],"tags":["fleet","conflict","posture"],"backgroundTag":"conflicting-config-options","analyzedSha":"73e0f67d83c59909b571efdfc88c4bc28c309cb1","analyzedAt":"2026-09-22T01:30:00.501Z","contentChangedAt":"2026-09-22T01:30:00.501Z","schemaVersion":2},"datasetVersion":"2026-09-22T10:30:35.592Z"}