Hmbown/CodeWhale · error · anyhow::Error

Invalid auto_review.{kind}[{index}]: set at least one of too

Error message

Invalid auto_review.{kind}[{index}]: set at least one of tool or action_kind.

What it means

An auto_review rule has no matcher at all: neither tool nor action_kind is set (has_matcher() false, crates/tui/src/config.rs:3035). Such a rule would match nothing (or everything, depending on interpretation), so validation rejects it at config load.

Source

Thrown at crates/tui/src/config.rs:3035

                .action_kind
                .as_deref()
                .is_some_and(|value| !value.trim().is_empty())
    }
}

fn validate_auto_review_rules(kind: &str, rules: &[AutoReviewRuleConfig]) -> Result<()> {
    for (index, rule) in rules.iter().enumerate() {
        if rule
            .text_contains
            .as_deref()
            .is_some_and(|value| !value.trim().is_empty())
        {
            anyhow::bail!(
                "Invalid auto_review.{kind}[{index}].text_contains: user-intent matching was retired; scope the rule with tool and/or action_kind."
            );
        }
        if !rule.has_matcher() {
            anyhow::bail!(
                "Invalid auto_review.{kind}[{index}]: set at least one of tool or action_kind."
            );
        }
        if let Some(action_kind) = rule.action_kind.as_deref() {
            let normalized = action_kind.trim().to_ascii_lowercase().replace('-', "_");
            if parse_auto_review_action_kind(&normalized).is_none() {
                anyhow::bail!(
                    "Invalid auto_review.{kind}[{index}].action_kind '{action_kind}': expected read, write, shell, external, publish, or destructive."
                );
            }
            if kind == "allow"
                && !matches!(
                    normalized.as_str(),
                    "read" | "write" | "shell" | "external" | "publish" | "destructive"
                )
            {
                anyhow::bail!(
                    "Invalid auto_review.allow[{index}].action_kind '{action_kind}': this retired narrow kind cannot safely widen to a v0.9.8 decision class; replace it with an exact tool rule or a current action_kind."

View on GitHub (pinned to 8880682c63)

Solutions

  1. Add tool = "<tool-id>" or action_kind = "<kind>" to the rule
  2. Check for typo'd keys (tool vs tools) that leave the matcher empty
  3. Remove the rule entirely if it was vestigial

Example fix

# config.toml - before
[[auto_review.ask]]
comment = "check pushes"

# config.toml - after
[[auto_review.ask]]
tool = "bash"
action_kind = "publish"
Defensive patterns

Strategy: validation

Validate before calling

fn rule_has_matcher(r: &AutoReviewRuleConfig) -> bool {
    r.tool.as_deref().is_some_and(|t| !t.trim().is_empty())
        || r.action_kind.is_some()
}

Type guard

fn is_matcherless_rule_error(msg: &str) -> bool {
    msg.contains("set at least one of tool or action_kind")
}

Try / catch

// At config-build time, drop or fix matcherless rules explicitly
rules.retain(|r| {
    if !rule_has_matcher(r) { warn!("dropping matcherless rule {r:?}"); false } else { true }
});

Prevention

When it happens

Trigger: A rule that only had text_contains (already stripped by the earlier check), a rule written with only metadata like review_level or comment, or a typo'd key that silently deserialized to nothing.

Common situations: Migrating old configs rule-by-rule; typos in key names (tools instead of tool) that serde ignores.

Related errors


AI-assisted analysis of Hmbown/CodeWhale@8880682c63 (2026-08-16). Data as JSON: /api/errors/1e07ed5605f9e72a. Report an issue: GitHub.