zeroclaw-labs/zeroclaw · error

Unknown OTP domain category '{category}'. Known categories:

Error message

Unknown OTP domain category '{category}'. Known categories: {known}

What it means

DomainMatcher::expand_categories maps named OTP domain categories to their built-in pattern lists. Exactly four categories exist in DOMAIN_CATEGORIES: banking, medical, government, identity_providers. Names are trimmed and lowercased before lookup, so casing never fails — only a genuine typo or unknown name does, and the error lists every valid name.

Source

Thrown at crates/zeroclaw-config/src/domain_matcher.rs:87

        self.patterns
            .iter()
            .any(|pattern| domain_matches_pattern(pattern, &normalized_domain))
    }

    pub fn expand_categories(categories: &[String]) -> Result<Vec<String>> {
        let mut expanded = Vec::new();
        for category in categories {
            let normalized = category.trim().to_ascii_lowercase();
            let Some((_, domains)) = DOMAIN_CATEGORIES
                .iter()
                .find(|(name, _)| *name == normalized.as_str())
            else {
                let known = DOMAIN_CATEGORIES
                    .iter()
                    .map(|(name, _)| *name)
                    .collect::<Vec<_>>()
                    .join(", ");
                bail!("Unknown OTP domain category '{category}'. Known categories: {known}");
            };
            expanded.extend(domains.iter().map(|domain| (*domain).to_string()));
        }
        Ok(expanded)
    }

    pub fn validate_pattern(pattern: &str) -> Result<()> {
        let _ = normalize_pattern(pattern)?;
        Ok(())
    }
}

fn normalize_domain(raw: &str) -> Option<String> {
    let mut domain = raw.trim().to_ascii_lowercase();
    if domain.is_empty() {
        return None;
    }

View on GitHub (pinned to 88bb9c8533)

Solutions

  1. Use one of the exact names: banking, medical, government, identity_providers
  2. Pre-validate the config's category list against the four known names at load time
  3. If no built-in category fits, enumerate the concrete domain patterns in gated_domains instead of inventing a category

Example fix

# before
categories = ["identity-provider"]  # bails

# after
categories = ["identity_providers"]
Defensive patterns

Strategy: validation

Validate before calling

const KNOWN_CATEGORIES: &[&str] = &["banking", "medical", "government", "identity_providers"];

for c in &categories {
    let n = c.trim().to_ascii_lowercase();
    if !KNOWN_CATEGORIES.contains(&n.as_str()) {
        anyhow::bail!("unknown OTP category {c}; expected one of {KNOWN_CATEGORIES:?}");
    }
}
let matcher = DomainMatcher::new(&gated, &categories)?;

Type guard

fn is_known_category(name: &str) -> bool {
    let n = name.trim().to_ascii_lowercase();
    matches!(n.as_str(), "banking" | "medical" | "government" | "identity_providers")
}

Try / catch

match DomainMatcher::new(&gated, &categories) {
    Ok(m) => m,
    Err(e) if e.to_string().contains("Unknown OTP domain category") => {
        anyhow::bail!("config error: {e}; fix the otp categories list")
    }
    Err(e) => return Err(e),
}

Prevention

When it happens

Trigger: Calling `DomainMatcher::new(gated_domains, categories)` or `expand_categories(categories)` with any string outside {banking, medical, government, identity_providers} — e.g. "identity-provider" (hyphen instead of underscore) or "finance".

Common situations: Hand-editing the OTP-gated domains config and guessing at category names; copying a category from older docs or a different tool's vocabulary; hyphen/underscore confusion from slug-style naming conventions elsewhere in the config.

Related errors


AI-assisted analysis of zeroclaw-labs/zeroclaw@88bb9c8533 (2026-08-23). Data as JSON: /api/errors/294d7b53b8a81ab3. Report an issue: GitHub.