zeroclaw-labs/zeroclaw · error

invalid memory.policy.redact_categories value(s)

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.

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.

Appendix: 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)