{"record":{"id":"fa705810711dbea4","repo":"Hmbown/CodeWhale","slug":"append-allow-rules-only-accepts-action-allow","errorCode":null,"errorMessage":"append_allow_rules only accepts action = \"allow\"","messagePattern":"append_allow_rules only accepts action = \"allow\"","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"crates/config/src/lib.rs","lineNumber":5329,"sourceCode":"    /// `permissions.toml` file.\n    ///\n    /// Existing comments and formatting are preserved. Exact duplicate rules\n    /// are ignored, and the in-memory permissions snapshot is refreshed after\n    /// a successful write.\n    pub fn append_ask_rules(&mut self, rules: &[ToolAskRule]) -> Result<usize> {\n        self.append_permission_rules(rules, PermissionAction::Ask)\n    }\n\n    /// Atomically append exact, repo-scoped allow rules to the sibling\n    /// `permissions.toml` file.\n    ///\n    /// The caller is responsible for deciding which tool calls are eligible;\n    /// this boundary rejects broad or incorrectly typed records so a UI bug\n    /// cannot persist an unscoped allow grant.\n    pub fn append_allow_rules(&mut self, rules: &[ToolAskRule]) -> Result<usize> {\n        for rule in rules {\n            if rule.action != PermissionAction::Allow {\n                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\");","sourceCodeStart":5311,"sourceCodeEnd":5347,"githubUrl":"https://github.com/Hmbown/CodeWhale/blob/73e0f67d83c59909b571efdfc88c4bc28c309cb1/crates/config/src/lib.rs#L5311-L5347","documentation":"append_allow_rules persists tool permission rules, but only allow-grants: every rule must have action == PermissionAction::Allow. Any deny/ask rule passed to this boundary is rejected so a UI bug cannot persist an unscoped or wrongly typed grant.","triggerScenarios":"Calling append_allow_rules with a ToolAskRule whose action is not Allow — e.g. passing captured ask/deny records straight through from a permission prompt handler.","commonSituations":"A permission UI batching all displayed rules (including denials) into one persist call; reusing a generic rule-append API for policy rules; a refactor that changed the action enum on existing records.","solutions":["Filter the input to rules with action == PermissionAction::Allow before calling","Route deny/ask rules to their intended persistence path instead","Fix the caller that built the rule list if Allow rules are arriving with the wrong action"],"exampleFix":"// before\nconfig.append_allow_rules(&all_prompt_rules)?;\n// after\nlet allows: Vec<_> = all_prompt_rules.iter().filter(|r| r.action == PermissionAction::Allow).cloned().collect();\nconfig.append_allow_rules(&allows)?;","handlingStrategy":"validation","validationCode":"if rules.iter().any(|r| r.action != PermissionAction::Allow) {\n    return Err(\"append_allow_rules accepts only action = allow rules\");\n}","typeGuard":"fn is_allow_rule(r: &ToolAskRule) -> bool { r.action == PermissionAction::Allow }","tryCatchPattern":"match config.append_allow_rules(&rules) {\n    Err(e) if e.to_string().contains(\"only accepts action\") => eprintln!(\"filter to Allow rules before persisting\"),\n    other => other?,\n}","preventionTips":["Filter by action at the call site, not inside the API","Keep allow/deny/ask persistence paths separate","Add a regression test that mixed-action batches are rejected"],"tags":["permissions","security","validation"],"backgroundTag":"invalid-enum-value","analyzedSha":"73e0f67d83c59909b571efdfc88c4bc28c309cb1","analyzedAt":"2026-09-22T01:30:00.501Z","contentChangedAt":"2026-09-22T01:30:00.501Z","schemaVersion":2},"datasetVersion":"2026-09-22T11:17:16.035Z"}