{"record":{"id":"24d8d5e6d412c29c","repo":"Hmbown/CodeWhale","slug":"invalid-auto-review-kind-index-text-contains","errorCode":null,"errorMessage":"Invalid auto_review.{kind}[{index}].text_contains: user-intent matching was retired; scope the rule with tool and/or action_kind.","messagePattern":"Invalid auto_review\\.(.+?)\\[(.+?)\\]\\.text_contains: user-intent matching was retired; scope the rule with tool and/or action_kind\\.","errorType":"validation","errorClass":"anyhow::Error","httpStatus":null,"severity":"error","filePath":"crates/tui/src/config.rs","lineNumber":3030,"sourceCode":"    fn has_matcher(&self) -> bool {\n        self.tool\n            .as_deref()\n            .is_some_and(|value| !value.trim().is_empty())\n            || self\n                .action_kind\n                .as_deref()\n                .is_some_and(|value| !value.trim().is_empty())\n    }\n}\n\nfn validate_auto_review_rules(kind: &str, rules: &[AutoReviewRuleConfig]) -> Result<()> {\n    for (index, rule) in rules.iter().enumerate() {\n        if rule\n            .text_contains\n            .as_deref()\n            .is_some_and(|value| !value.trim().is_empty())\n        {\n            anyhow::bail!(\n                \"Invalid auto_review.{kind}[{index}].text_contains: user-intent matching was retired; scope the rule with tool and/or action_kind.\"\n            );\n        }\n        if !rule.has_matcher() {\n            anyhow::bail!(\n                \"Invalid auto_review.{kind}[{index}]: set at least one of tool or action_kind.\"\n            );\n        }\n        if let Some(action_kind) = rule.action_kind.as_deref() {\n            let normalized = action_kind.trim().to_ascii_lowercase().replace('-', \"_\");\n            if parse_auto_review_action_kind(&normalized).is_none() {\n                anyhow::bail!(\n                    \"Invalid auto_review.{kind}[{index}].action_kind '{action_kind}': expected read, write, shell, external, publish, or destructive.\"\n                );\n            }\n            if kind == \"allow\"\n                && !matches!(\n                    normalized.as_str(),","sourceCodeStart":3012,"sourceCodeEnd":3048,"githubUrl":"https://github.com/Hmbown/CodeWhale/blob/8880682c63083a91624de936797efa3ce9e498fd/crates/tui/src/config.rs#L3012-L3048","documentation":"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.","triggerScenarios":"Carrying a pre-retirement config forward after an upgrade; hand-writing a rule keyed on prompt text; docs or snippets from an older version.","commonSituations":"Version upgrades where the old matcher was removed; copied config from another machine or blog post.","solutions":["Delete text_contains from the rule","Scope the same intent with tool = \"<tool-id>\" and/or action_kind = \"read|write|shell|external|publish|destructive\"","Re-load the config to confirm validation passes"],"exampleFix":"# config.toml - before\n[[auto_review.deny]]\ntext_contains = \"rm -rf\"\n\n# config.toml - after\n[[auto_review.deny]]\ntool = \"bash\"\naction_kind = \"destructive\"\n","handlingStrategy":"validation","validationCode":"// Config linter pass over rules before load\nfor r in &cfg.auto_review.deny {\n    assert!(r.text_contains.is_none(), \"text_contains is retired; use tool/action_kind\");\n}","typeGuard":"fn uses_retired_matcher(rule: &AutoReviewRuleConfig) -> bool {\n    rule.text_contains.as_deref().is_some_and(|v| !v.trim().is_empty())\n}","tryCatchPattern":"// Fail config load with the rule's index so the user can jump to it\nif uses_retired_matcher(&rule) {\n    return Err(anyhow!(\"auto_review[{i}] uses retired text_contains\"));\n}","preventionTips":["After upgrades, run the config validator before launching","Scope review rules to tools or action kinds, never to prompt text"],"tags":["config","auto-review","validation","migration"],"backgroundTag":null,"analyzedSha":"8880682c63083a91624de936797efa3ce9e498fd","analyzedAt":"2026-08-16T11:31:27.956Z","schemaVersion":2},"datasetVersion":"2026-08-16T13:17:31.715Z"}