{"record":{"id":"7f6d237d3c2db9d9","repo":"Hmbown/CodeWhale","slug":"persistent-command-allow-rules-must-not-be-empty","errorCode":null,"errorMessage":"persistent command allow rules must not be empty","messagePattern":"persistent command allow rules must not be empty","errorType":"validation","errorClass":"anyhow::Error","httpStatus":null,"severity":"error","filePath":"crates/config/src/lib.rs","lineNumber":5272,"sourceCode":"                bail!(\"append_allow_rules only accepts action = \\\"allow\\\"\");\n            }\n            let Some(workspace) = rule\n                .workspace\n                .as_deref()\n                .and_then(codewhale_execpolicy::normalize_workspace_scope)\n            else {\n                bail!(\"persistent allow rules must be scoped to a workspace\");\n            };\n            if rule.command.is_some() && !rule.command_exact {\n                bail!(\"persistent command allow rules must use exact matching\");\n            }\n            if rule.command.is_none() && rule.path.is_none() {\n                bail!(\"persistent allow rules must match an exact command or path\");\n            }\n            if let Some(command) = rule.command.as_deref()\n                && command.trim().is_empty()\n            {\n                bail!(\"persistent command allow rules must not be empty\");\n            }\n            if let Some(path) = rule.path.as_deref()\n                && codewhale_execpolicy::normalize_workspace_relative_path(path, &workspace)\n                    .is_none_or(|path| path.is_empty())\n            {\n                bail!(\"persistent path allow rules must stay within the workspace\");\n            }\n        }\n        self.append_permission_rules(rules, PermissionAction::Allow)\n    }\n\n    fn append_permission_rules(\n        &mut self,\n        rules: &[ToolAskRule],\n        expected_action: PermissionAction,\n    ) -> Result<usize> {\n        if rules.is_empty() {\n            return Ok(0);","sourceCodeStart":5254,"sourceCodeEnd":5290,"githubUrl":"https://github.com/Hmbown/CodeWhale/blob/0c42157ee52f9d55af2b506d71b46249910f77d3/crates/config/src/lib.rs#L5254-L5290","documentation":"append_allow_rules (crates/config/src/lib.rs:5272) rejects a command allow rule whose command string trims to empty. An empty exact-match command is either a bug or a trivially-bypassed grant, so it never reaches permissions.toml.","triggerScenarios":"A ToolAskRule with command = Some(\"\" or \"   \") and command_exact = true passed to append_allow_rules — usually an unpopulated form field or a deserialized default.","commonSituations":"Automation persisting an approval event before the command string is filled in, string manipulation producing an empty command (e.g. splitting a shell line incorrectly).","solutions":["Populate the exact non-empty command string before persisting","Trim input at the UI boundary and reject empty submissions there","If the command is genuinely empty, the rule has no meaning — drop it"],"exampleFix":"// before\nlet rule = ToolAskRule { command: Some(String::new()), command_exact: true, /* ... */ };\n\n// after\nlet rule = ToolAskRule { command: Some(approved_command.trim().to_owned()), command_exact: true, /* ... */ };","handlingStrategy":"validation","validationCode":"assert!(rule.command.as_deref().is_none_or(|c| !c.trim().is_empty())); // before append","typeGuard":"fn is_nonempty_command_rule(rule: &ToolAskRule) -> bool {\n    rule.command.as_deref().is_none_or(|c| !c.trim().is_empty())\n}","tryCatchPattern":null,"preventionTips":["Reject empty command fields at the form boundary","Trim user input when constructing rules","Never persist rules from partially-populated approval events"],"tags":["rust","permissions","security","validation","empty-value"],"backgroundTag":"permission-rule-validation-failed","analyzedSha":"0c42157ee52f9d55af2b506d71b46249910f77d3","analyzedAt":"2026-08-20T21:50:45.477Z","schemaVersion":2},"datasetVersion":"2026-08-21T13:17:26.733Z"}