zeroclaw-labs/zeroclaw · error

invalid memory.policy.redact_categories value(s): {}; expect

Error message

invalid memory.policy.redact_categories value(s): {}; expected secret, api_key, private_key, email, or phone

What it means

When redact_on_write is enabled, redaction_categories() maps every entry of [memory.policy].redact_categories through RedactCategory::from_config; accepted values are secret, api_key, private_key, email, phone. All unknown entries are collected and reported together in one error listing the offenders.

Source

Thrown at crates/zeroclaw-memory/src/scanned.rs:137

    /// Scope for read-time re-scanning; `None` disables read filtering.
    fn read_scope(&self) -> anyhow::Result<Option<Scope>> {
        if !self.policy.threat_scan_load_time {
            return Ok(None);
        }
        self.scan_scope()
    }

    fn redaction_categories(&self) -> anyhow::Result<Vec<RedactCategory>> {
        let mut parsed = Vec::with_capacity(self.policy.redact_categories.len());
        let mut invalid = Vec::new();
        for category in &self.policy.redact_categories {
            match RedactCategory::from_config(category) {
                Some(category) => parsed.push(category),
                None => invalid.push(category.as_str()),
            }
        }
        if !invalid.is_empty() {
            anyhow::bail!(
                "invalid memory.policy.redact_categories value(s): {}; expected secret, api_key, private_key, email, or phone",
                invalid.join(",")
            );
        }
        Ok(parsed)
    }

    /// Candidate count to request from the backend for a read-time filtered
    /// recall of `limit` rows. See [`READ_REFILL_MULTIPLIER`] for the bound.
    fn read_fetch_limit(limit: usize) -> usize {
        if limit == 0 {
            return 0;
        }
        limit.saturating_mul(READ_REFILL_MULTIPLIER).max(limit)
    }

    /// Run the write-boundary pipeline on one content payload: redact
    /// configured categories (when `redact_on_write` is enabled), then scan

View on GitHub (pinned to 88bb9c8533)

Solutions

  1. Rename or remove invalid entries so only the five supported categories remain
  2. Leave redact_categories empty (with redact_on_write still true) to disable category redaction without touching the flag
  3. Restart after editing and test with a single store call
  4. Validate the list at startup when redaction is enabled

Example fix

# before
[memory.policy]
redact_on_write = true
redact_categories = ["phone_number", "emails"]

# after
[memory.policy]
redact_on_write = true
redact_categories = ["phone", "email"]   # secret | api_key | private_key | email | phone
Defensive patterns

Strategy: validation

Validate before calling

const VALID: [&str; 5] = ["secret", "api_key", "private_key", "email", "phone"];
for c in &cfg.memory.policy.redact_categories {
    anyhow::ensure!(VALID.contains(&c.trim().to_ascii_lowercase().as_str()), "invalid redact category {c}");
}

Type guard

fn is_valid_redact_category(v: &str) -> bool {
    matches!(v, "secret" | "api_key" | "private_key" | "email" | "phone")
}

Try / catch

if let Err(e) = memory.store(k, v).await {
    if e.to_string().contains("invalid memory.policy.redact_categories value") { fix_config_and_restart(); }
    return Err(e);
}

Prevention

When it happens

Trigger: redact_on_write = true together with category names like phone_number, credit_card, credentials, or email_address; parsing happens on the write path, so it fails on the first store after enabling redaction.

Common situations: Renamed categories across zeroclaw versions; guessed category names; template configs left with placeholder entries.

Understand the failure class

Background: "Invalid value" and "allowed values are" config errors: what your library rejected and how to fix it — this error's family across 41 libraries.

Related errors


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