{"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":null,"httpStatus":null,"severity":"error","filePath":"crates/tui/src/runtime_threads.rs","lineNumber":7882,"sourceCode":"        let _config_admission = self.config_admission.read().await;\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        // Source resolution reads config files. Do it off the Tokio worker,","sourceCodeStart":7864,"sourceCodeEnd":7900,"githubUrl":"https://github.com/Hmbown/CodeWhale/blob/73e0f67d83c59909b571efdfc88c4bc28c309cb1/crates/tui/src/runtime_threads.rs#L7864-L7900","documentation":"When a thread-update request includes a `model`, the value must not be whitespace-only/empty. The guard rejects `Some(\"\")` or `Some(\"   \")` so a thread never ends up with a meaningless model assignment.","triggerScenarios":"Calling update-thread with `model: Some(\"\".into())` or `Some(\" \\t\".into())`; a client form submitted with the model field cleared but still serialized as an empty string.","commonSituations":"An UI clears the model dropdown and sends \"\" instead of `null`; a config export round-trip loses the model value; JSON body `{\"model\": \"\"}` from an API consumer.","solutions":["Pass `None` to leave the model unchanged instead of `Some(\"\")`.","Trim the incoming string and, if empty after trim, drop the field from the request.","Validate the model string against the registry before sending the update."],"exampleFix":"// before\nUpdateThreadRequest { model: Some(user_input), .. }\n\n// after\nlet model = user_input.trim().to_string();\nUpdateThreadRequest { model: (!model.is_empty()).then_some(model), .. }","handlingStrategy":"validation","validationCode":"let model = req.model.as_deref().map(str::trim).filter(|m| !m.is_empty());","typeGuard":"fn valid_model_field(m: &Option<String>) -> bool {\n    m.as_deref().map_or(true, |s| !s.trim().is_empty())\n}","tryCatchPattern":null,"preventionTips":["Normalize empty strings to None at the client boundary","Validate model names against the registry before updates","Never serialize cleared form fields as \"\" — omit them"],"tags":["validation","empty-string","api"],"backgroundTag":"invalid-argument-value","analyzedSha":"73e0f67d83c59909b571efdfc88c4bc28c309cb1","analyzedAt":"2026-09-22T01:30:00.501Z","contentChangedAt":"2026-09-22T01:30:00.501Z","schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}