Hmbown/CodeWhale · error · anyhow::Error

persistent allow rules must match an exact command or path

Error message

persistent allow rules must match an exact command or path

What it means

append_allow_rules (crates/config/src/lib.rs:5267) rejects an allow rule with neither command nor path set. A tool-only allow ('always allow Bash') is too broad to persist, so at least one exact matcher (command or path) is mandatory; otherwise the bail fires before anything is written.

Source

Thrown at crates/config/src/lib.rs:5267

    /// this boundary rejects broad or incorrectly typed records so a UI bug
    /// cannot persist an unscoped allow grant.
    pub fn append_allow_rules(&mut self, rules: &[ToolAskRule]) -> Result<usize> {
        for rule in rules {
            if rule.action != PermissionAction::Allow {
                bail!("append_allow_rules only accepts action = \"allow\"");
            }
            let Some(workspace) = rule
                .workspace
                .as_deref()
                .and_then(codewhale_execpolicy::normalize_workspace_scope)
            else {
                bail!("persistent allow rules must be scoped to a workspace");
            };
            if rule.command.is_some() && !rule.command_exact {
                bail!("persistent command allow rules must use exact matching");
            }
            if rule.command.is_none() && rule.path.is_none() {
                bail!("persistent allow rules must match an exact command or path");
            }
            if let Some(command) = rule.command.as_deref()
                && command.trim().is_empty()
            {
                bail!("persistent command allow rules must not be empty");
            }
            if let Some(path) = rule.path.as_deref()
                && codewhale_execpolicy::normalize_workspace_relative_path(path, &workspace)
                    .is_none_or(|path| path.is_empty())
            {
                bail!("persistent path allow rules must stay within the workspace");
            }
        }
        self.append_permission_rules(rules, PermissionAction::Allow)
    }

    fn append_permission_rules(
        &mut self,

View on GitHub (pinned to 0c42157ee5)

Solutions

  1. Scope the rule: attach the exact command or the workspace-relative path that was approved
  2. Keep blanket tool approvals session-only
  3. Offer 'always allow <tool> <command>' phrasing in the prompt UI so persisted rules always carry a matcher

Example fix

// before
let rule = ToolAskRule { command: None, path: None, tool: tool.clone(), /* ... */ };

// after
let rule = ToolAskRule { command: Some("cargo test".into()), path: None, tool: tool.clone(), /* ... */ };
Defensive patterns

Strategy: validation

Validate before calling

assert!(rule.command.is_some() || rule.path.is_some()); // tool-only rules cannot persist

Type guard

fn has_persistable_matcher(rule: &ToolAskRule) -> bool {
    rule.command.is_some() || rule.path.is_some()
}

Prevention

When it happens

Trigger: A ToolAskRule with command = None and path = None (tool-scoped blanket approval) passed to append_allow_rules.

Common situations: User clicks 'always allow this tool' and the caller tries to persist it without a matcher; rules built from a template that leaves both optional fields empty.

Related errors


AI-assisted analysis of Hmbown/CodeWhale@0c42157ee5 (2026-08-20). Data as JSON: /api/errors/61851a5f4c7684d5. Report an issue: GitHub.