{"record":{"id":"652f0a34cea13e09","repo":"Hmbown/CodeWhale","slug":"model-must-not-be-empty","errorCode":null,"errorMessage":"model must not be empty","messagePattern":"model must not be empty","errorType":"validation","errorClass":"anyhow::Error","httpStatus":null,"severity":"warning","filePath":"crates/tui/src/runtime_threads.rs","lineNumber":4645,"sourceCode":"    pub async fn update_thread(&self, id: &str, req: UpdateThreadRequest) -> Result<ThreadRecord> {\n        if req.archived.is_none()\n            && req.allow_shell.is_none()\n            && req.trust_mode.is_none()\n            && req.auto_approve.is_none()\n            && req.model.is_none()\n            && req.mode.is_none()\n            && req.permission_posture.is_none()\n            && req.title.is_none()\n            && req.system_prompt.is_none()\n            && req.workspace.is_none()\n        {\n            bail!(\"At least one thread field is required\");\n        }\n\n        if let Some(model) = req.model.as_ref()\n            && model.trim().is_empty()\n        {\n            bail!(\"model must not be empty\");\n        }\n        if let Some(mode) = req.mode.as_ref()\n            && mode.trim().is_empty()\n        {\n            bail!(\"mode must not be empty\");\n        }\n        if let Some(permission_posture) = req.permission_posture.as_ref()\n            && permission_posture.trim().is_empty()\n        {\n            bail!(\"permission_posture must not be empty\");\n        }\n        if let Some(workspace) = req.workspace.as_ref()\n            && workspace.as_os_str().is_empty()\n        {\n            bail!(\"workspace must not be empty\");\n        }\n\n        let configured_sandbox_mode = self.read_config().sandbox_mode.clone();","sourceCodeStart":4627,"sourceCodeEnd":4663,"githubUrl":"https://github.com/Hmbown/CodeWhale/blob/0c42157ee52f9d55af2b506d71b46249910f77d3/crates/tui/src/runtime_threads.rs#L4627-L4663","documentation":"update_thread was called with Some(model) whose value is empty or whitespace-only. Model is the field that selects the provider route for the thread, and a blank model would silently resolve to a broken route, so it is rejected before any durable change.","triggerScenarios":"UpdateThreadRequest { model: Some(\"\"), .. } or Some(\"   \") - typically a form field cleared by the user, a trim-then-assign bug, or JSON that sends model: \"\" to mean 'unset'. Check at runtime_threads.rs:4642-4646 (sibling guards cover mode, permission_posture, workspace).","commonSituations":"A settings UI that serializes empty inputs as empty strings instead of omitting the key; migration scripts copying a missing model as \"\"; clients intending to clear/reset the model - the API has no 'clear', only 'set'.","solutions":["Omit the model field (None) when the user clears the input instead of sending an empty string","Trim input client-side and treat empty-as-unchanged in the UI layer","If a model change is intended, send a valid model identifier from the configured registry","Apply the same discipline to mode, permission_posture, and workspace - they have identical non-empty guards"],"exampleFix":"// before\nlet req = UpdateThreadRequest { model: Some(input.clone()), ..Default::default() }; // input == \"\"\n\n// after\nlet req = UpdateThreadRequest {\n    model: (!input.trim().is_empty()).then(|| input.trim().to_string()),\n    ..Default::default()\n};","handlingStrategy":"validation","validationCode":"// Normalize: empty means 'omit the field'.\nlet req = UpdateThreadRequest {\n    model: model_input.trim().is_empty().then(|| model_input.trim().to_string()),\n    ..Default::default()\n};\nif req.model.is_none() && req.title.is_none() /* ... */ {\n    return Ok(current); // nothing to change\n}\nmanager.update_thread(id, req).await?;","typeGuard":"fn is_blank(value: &Option<String>) -> bool {\n    value.as_ref().is_some_and(|v| v.trim().is_empty())\n}","tryCatchPattern":null,"preventionTips":["Omit (None) fields the user cleared; the API has 'set', not 'clear'","Trim strings and drop empty ones before building the request","The same non-empty rule applies to mode, permission_posture, and workspace"],"tags":["thread-management","request-validation","model-config","empty-string"],"backgroundTag":"empty-string-field-validation","analyzedSha":"0c42157ee52f9d55af2b506d71b46249910f77d3","analyzedAt":"2026-08-20T21:50:45.477Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-14T00:17:10.932Z"}