{"record":{"id":"8fd36ed9e616ee1e","repo":"Hmbown/CodeWhale","slug":"permission-posture-must-not-be-empty","errorCode":null,"errorMessage":"permission_posture must not be empty","messagePattern":"permission_posture must not be empty","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"crates/tui/src/runtime_threads.rs","lineNumber":7892,"sourceCode":"            && 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,\n        // then recheck the conversation identity before committing the grant.\n        let shell_policy_workspace = if req.allow_shell == Some(true) || req.workspace.is_some() {\n            let current = self.get_thread(id).await?;\n            if req.allow_shell.unwrap_or(current.allow_shell) {\n                let workspace = req.workspace.clone().unwrap_or(current.workspace);\n                self.validate_shell_access_policy(&workspace, config_path, config_profile)\n                    .await?;\n                Some(workspace)\n            } else {\n                None","sourceCodeStart":7874,"sourceCodeEnd":7910,"githubUrl":"https://github.com/Hmbown/CodeWhale/blob/73e0f67d83c59909b571efdfc88c4bc28c309cb1/crates/tui/src/runtime_threads.rs#L7874-L7910","documentation":"When a thread-update request includes a `permission_posture`, the value must not be empty or whitespace-only. The posture drives permission decisions for the thread, so a blank value would silently disable or corrupt permission policy.","triggerScenarios":"Calling update-thread with `permission_posture: Some(\"\".into())` or a whitespace-only string; a client sends `{\"permission_posture\": \"\"}`.","commonSituations":"A settings UI clears a posture dropdown; automation copies an empty posture field from another thread; a config merge produces empty strings for unset enum-like fields.","solutions":["Pass `None` to leave the current posture unchanged.","Use one of the library's named postures rather than a free-form string.","Trim-and-drop or validate against the supported posture set before sending."],"exampleFix":"// before\nUpdateThreadRequest { permission_posture: Some(posture), .. } // may be \"\"\n\n// after\nUpdateThreadRequest { permission_posture: Some(posture).filter(|p| !p.trim().is_empty()), .. }","handlingStrategy":"validation","validationCode":"if let Some(p) = &req.permission_posture { if p.trim().is_empty() { req.permission_posture = None; } }","typeGuard":"fn valid_posture(p: &str) -> bool { !p.trim().is_empty() }","tryCatchPattern":null,"preventionTips":["Use the library's named posture constants instead of free strings","Validate posture against supported values before the call","Treat blank input as 'unchanged' and omit the field"],"tags":["validation","permissions","empty-string"],"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"}