Hmbown/CodeWhale · error · anyhow::Error

Invalid auto_review.{kind}[{index}].text_contains: user-inte

Error message

Invalid auto_review.{kind}[{index}].text_contains: user-intent matching was retired; scope the rule with tool and/or action_kind.

What it means

Config validation rejects auto_review rules that still set text_contains (crates/tui/src/config.rs:3030): user-intent matching was retired, so a rule must be scoped by tool and/or action_kind instead. This fails config load/startup, not at review time.

Source

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

    fn has_matcher(&self) -> bool {
        self.tool
            .as_deref()
            .is_some_and(|value| !value.trim().is_empty())
            || self
                .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(),

View on GitHub (pinned to 8880682c63)

Solutions

  1. Delete text_contains from the rule
  2. Scope the same intent with tool = "<tool-id>" and/or action_kind = "read|write|shell|external|publish|destructive"
  3. Re-load the config to confirm validation passes

Example fix

# config.toml - before
[[auto_review.deny]]
text_contains = "rm -rf"

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

Strategy: validation

Validate before calling

// Config linter pass over rules before load
for r in &cfg.auto_review.deny {
    assert!(r.text_contains.is_none(), "text_contains is retired; use tool/action_kind");
}

Type guard

fn uses_retired_matcher(rule: &AutoReviewRuleConfig) -> bool {
    rule.text_contains.as_deref().is_some_and(|v| !v.trim().is_empty())
}

Try / catch

// Fail config load with the rule's index so the user can jump to it
if uses_retired_matcher(&rule) {
    return Err(anyhow!("auto_review[{i}] uses retired text_contains"));
}

Prevention

When it happens

Trigger: Carrying a pre-retirement config forward after an upgrade; hand-writing a rule keyed on prompt text; docs or snippets from an older version.

Common situations: Version upgrades where the old matcher was removed; copied config from another machine or blog post.

Related errors


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