{"record":{"id":"76e1535f57b74ba0","repo":"Hmbown/CodeWhale","slug":"automation-name-cannot-be-empty","errorCode":null,"errorMessage":"Automation name cannot be empty","messagePattern":"Automation name cannot be empty","errorType":"validation","errorClass":"anyhow::Error","httpStatus":null,"severity":"error","filePath":"crates/tui/src/automation_manager.rs","lineNumber":1045,"sourceCode":"                    CURRENT_AUTOMATION_SCHEMA_VERSION\n                );\n            }\n            out.push(record);\n        }\n        out.sort_by_key(|r| std::cmp::Reverse(r.updated_at));\n        Ok(out)\n    }\n\n    pub fn update_automation(\n        &self,\n        id: &str,\n        req: UpdateAutomationRequest,\n    ) -> Result<AutomationRecord> {\n        let mut existing = self.get_automation(id)?;\n\n        if let Some(name) = req.name {\n            if name.trim().is_empty() {\n                bail!(\"Automation name cannot be empty\");\n            }\n            existing.name = name.trim().to_string();\n        }\n        if let Some(prompt) = req.prompt {\n            if prompt.trim().is_empty() {\n                bail!(\"Automation prompt cannot be empty\");\n            }\n            existing.prompt = prompt.trim().to_string();\n        }\n        if let Some(rrule) = req.rrule {\n            let normalized = rrule.trim().to_ascii_uppercase();\n            AutomationSchedule::parse_rrule(&normalized)?;\n            existing.rrule = normalized;\n            if matches!(existing.status, AutomationStatus::Active) {\n                let schedule = AutomationSchedule::parse_rrule(&existing.rrule)?;\n                existing.next_run_at =\n                    Some(schedule.next_after_with_anchor(Utc::now(), existing.created_at)?);\n            }","sourceCodeStart":1027,"sourceCodeEnd":1063,"githubUrl":"https://github.com/Hmbown/CodeWhale/blob/0c42157ee52f9d55af2b506d71b46249910f77d3/crates/tui/src/automation_manager.rs#L1027-L1063","documentation":"update_automation rejects a name update whose value trims to empty. Only Some(name) triggers validation; omitting the field leaves the name unchanged. The trimmed value is what gets persisted.","triggerScenarios":"Calling update_automation with UpdateAutomationRequest { name: Some(String::new()), .. } or Some(\"   \".into()).","commonSituations":"Forms submitting whitespace-only edits; trim mismatches between client and manager; cleared input fields sent as empty strings instead of omitted.","solutions":["Send a non-empty name, or omit the field to keep the current name","Trim client-side and skip the update when the result is empty","Treat an emptied edit field as a no-op upstream of the request"],"exampleFix":"// before\nlet req = UpdateAutomationRequest { name: Some(\"   \".to_string()), ..Default::default() };\n\n// after (leave the name unchanged)\nlet req = UpdateAutomationRequest { name: None, ..Default::default() };","handlingStrategy":"validation","validationCode":"fn name_update_ok(name: &Option<String>) -> bool {\n    name.as_deref().is_none_or(|n| !n.trim().is_empty())\n}","typeGuard":"fn is_non_blank(s: &str) -> bool { !s.trim().is_empty() }","tryCatchPattern":null,"preventionTips":["Trim input client-side and skip the update when the result is empty","Distinguish 'no change' (omit the field) from 'set to empty' (invalid)","Reject whitespace-only form submissions before they reach the manager"],"tags":["rust","validation","empty-string","automation"],"backgroundTag":"required-field-empty","analyzedSha":"0c42157ee52f9d55af2b506d71b46249910f77d3","analyzedAt":"2026-08-20T21:50:45.477Z","schemaVersion":2},"datasetVersion":"2026-08-22T04:17:13.399Z"}