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
- Add tool = "<tool-id>" or action_kind = "<kind>" to the rule
- Check for typo'd keys (tool vs tools) that leave the matcher empty
- 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
- Watch for typo'd keys (tools vs tool) that serde silently ignores
- Template every new rule with a tool or action_kind from the start
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
- Invalid auto_review.{kind}[{index}].text_contains: user-inte
- Invalid auto_review.{kind}[{index}].action_kind '{action_kin
- Invalid auto_review.allow[{index}].action_kind '{action_kind
- ${name} is required
- ${names.join(" or ")} is required
AI-assisted analysis of Hmbown/CodeWhale@8880682c63 (2026-08-16).
Data as JSON: /api/errors/1e07ed5605f9e72a.
Report an issue: GitHub.